Merge branch 'develop' for 3.8 release

This commit is contained in:
Olof hagsand 2018-11-04 22:45:42 +01:00
commit 40fac27c2e
120 changed files with 6814 additions and 2863 deletions

3
.gitignore vendored
View file

@ -8,7 +8,6 @@ Makefile
apps/Makefile
apps/*/Makefile
docker/Makefile
docker/*/Makefile
etc/Makefile
example/Makefile
lib/Makefile
@ -37,8 +36,6 @@ docker/netconf/Dockerfile
etc/clixonrc
example/*.conf
include/clixon_config.h
lib/src/build.c

View file

@ -1,5 +1,109 @@
# Clixon Changelog
## 3.8.0 (Expected: Nov 4)
### Major New features
* YANG Features
* Yang 1.1 feature and if-feature according to RFC 7950 7.20.1 and 7.20.2.
* See https://github.com/clicon/clixon/issues/41
* Features are declared via CLICON_FEATURE in the configuration file. Example below shows enabling (1) a specific feature; (2) all features in a module; (3) all features in all modules:
```
<CLICON_FEATURE>ietf-routing:router-id</CLICON_FEATURE>
<CLICON_FEATURE>ietf-routing:*</CLICON_FEATURE>
<CLICON_FEATURE>*:*</CLICON_FEATURE>
```
* logical combination of features not implemented, eg if-feature "not foo or bar and baz";
* ietf-netconf yang module added with candidate, validate, startup and xpath features enabled.
* YANG module library
* YANG modules according to RFC 7895 and implemented by ietf-yang-library.yang
* Enabled by configuration option CLICON_MODULE_LIBRARY_RFC7895 - enabled by default
* RFC 7895 defines a module-set-id. Configure option CLICON_MODULE_SET_ID is set and changed when modules change.
* Yang 1.1 notification support (RFC 7950: Sec 7.16)
* New event streams implementation with replay
* See clicon_stream.[ch] for details
* Added stream discovery according to RFC 5277 for netconf and RFC 8040 for restconf
* Enabled by CLICON_STREAM_DISCOVERY_RFC5277 and CLICON_STREAM_DISCOVERY_RFC8040
* Configure option CLICON_STREAM_RETENTION is default number of seconds before dropping replay buffers
8040.
* Restconf stream notification support according to RFC8040
* See (apps/restconf/README.md) for more details.
* start-time and stop-time query parameters
* Fork fcgi handler for streams
* Set access/subscribe base URL with: CLICON_STREAM_URL (default "https://localhost") and CLICON_STREAM_PATH (default "streams")
* Example: new stream "foo" will get access URL: https://localhost/streams/foo
* Alternative variant using pub/sub support enabled by ./configure --enable-publish
* Set publish URL base with: CLICON_STREAM_PUB (default http://localhost/pub)
* Example: new stream "foo" will publish event streams on URL: https://localhost/pub/foo
* RFC8040 Restconf replay support: start-time and stop-time query parameter support
* This only applies to "native" restconf stream support, Nchan mode has different replay functionality
* RFC5277 Netconf replay supported
* Replay support is only in-memory and not persistent. External time-series DB could be added.
### API changes on existing features (you may need to change your code)
* Netconf hello capability updated to YANG 1.1 RFC7950 Sec 5.6.4
* Added urn:ietf:params:netconf:capability:yang-library:1.0
* Thanks @SCadilhac for helping out, see https://github.com/clicon/clixon/issues/39
* Major rewrite of event streams (as described above)
* If you used old event callbacks API, you need to switch to the streams API
* See clixon_stream.[ch]
* Old streams API which needs to be modified:
* clicon_log_register_callback() removed
* subscription_add() --> stream_add()
* stream_cb_add() --> stream_ss_add()
* stream_cb_delete() --> stream_ss_delete()
* backend_notify() and backend_notify_xml() - use stream_notify() instead
* Example uses "NETCONF" stream instead of "ROUTING"
* clixon_restconf and clixon_netconf changed to take -D `<level>` as command-line option instead of just -D (without debig level)
* This aligns to clixon_cli and clixon_backend
* Application command option -S to clixon_netconf is obsolete. Use `clixon_netconf -l s` instead.
* Unified log handling for all clicon applications using command-line option: `-l e|o|s|f<file>`.
* The options stand for e:stderr, o:stdout, s: syslog, f:file
* Added file logging (`-l f` or `-l f<file>`) for cases where neither syslog nor stderr is useful.
* Comply to RFC 8040 3.5.3.1 rule: api-identifier = [module-name ":"] identifier
* The "module-name" was a no-op before.
* This means that there was no difference between eg: GET /restconf/data/ietf-yang-library:modules-state and GET /restconf/data/foobar:modules-state
* Generilized top-level yang parsing functions
* Clarified semantics of main yang module:
* Command-line option -y MUST specify a filename
* Configure option CLICON_YANG_MODULE_MAIN MUST specify a module name
* yang_parse() changed to take either filename or module name and revision.
* Removed clicon_dbspec_name() and clicon_dbspec_name_set().
* Replace calls to yang_spec_main() with yang_spec_parse_module(). See for
example backend_main() and others if you need details.
### Minor changes
* Renamed test/test_auth*.sh tests to test/test_nacm*.sh
* YANG keywords "action" and "belongs-to" implemented by syntactically by parser (but not proper semantics).
* clixon-config YAML file has new revision: 2018-10-21.
* Allow new lines in CLI prompts
* uri_percent_encode() and xml_chardata_encode() changed to use stdarg parameters
* Added Configure option CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml as option and in example (so you dont need to provide -f command-line option).
* New function: clicon_conf_xml() returns configuration tree
* Obsoleted COMPAT_CLIV and COMPAT_XSL that were optional in 3.7
* Added command-line option `-t <timeout>` for clixon_netconf - quit after max time.
### Corrected Bugs
* No space after ampersand escaped characters in XML https://github.com/clicon/clixon/issues/52
* Thanks @SCadilhac
* Single quotes for XML attributes https://github.com/clicon/clixon/issues/51
* Thanks @SCadilhac
* Fixed https://github.com/clicon/clixon/issues/46 Issue with empty values in leaf-list
* Thanks achernavin22
* Identity without any identityref:s caused SEGV
* Memory error in backend transaction revert
* Set dir /www-data with www-data as owner, see https://github.com/clicon/clixon/issues/37
### Known issues
* Netconf RPC input is not sanity checked for wrong symbols (just ignored).
* Yang sub-command order and cardinality not checked.
* Top-level Yang symbol cannot be called "config" in any imported yang file.
* datastore uses "config" as reserved keyword for storing any XML whoich collides with code for detecting Yang sanity.
* Namespace name relabeling is not supported.
* Eg: if "des" is defined as prefix for an imported module, then a relabeling using xmlns is not supported, such as:
```
<crypto xmlns:x="urn:example:des">x:des3</crypto>
```
## 3.7.0 (22 July 2018)
### Major New features

View file

@ -67,12 +67,12 @@ depend:
install:
for i in $(SUBDIRS) doc; \
do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done; \
do (cd $$i; $(MAKE) $(MFLAGS) $@)||exit 1; done;
echo "Install for compilation by: make install-include"
install-include:
for i in $(SUBDIRS) doc; \
do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done; \
do (cd $$i && $(MAKE) $(MFLAGS) $@)||exit 1; done;
echo "To install example app: cd example; make; make install"
uninstall:

View file

@ -1,8 +1,8 @@
# Clixon
Clixon is an automatic configuration manager where you generate
interactive CLI, NETCONF, RESTCONF and embedded databases with
transaction support from a YANG specification.
Clixon is a YANG-based configuration manager, with interactive CLI,
NETCONF and RESTCONF interfaces, an embedded database and transaction
support.
* [Background](#background)
* [Frequently asked questions](doc/FAQ.md)
@ -16,7 +16,8 @@ transaction support from a YANG specification.
* [Netconf](#netconf)
* [Restconf](#restconf)
* [Datastore](datastore/README.md)
* [Authentication and Authorization](#auth)
* [Authentication](#auth)
* [NACM Access control](#nacm)
* [Example](example/)
* [Changelog](CHANGELOG.md)
* [Runtime](#runtime)
@ -107,24 +108,21 @@ specification for handling XML configuration data. The YANG spec is
used to generate an interactive CLI, netconf and restconf clients. It
also manages an XML datastore.
Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions:
- conformance: feature, if-feature, deviation
- list features: min/max-elements, unique
- action statements
- notifications
Clixon follows:
- [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt)
- [YANG 1.1 RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt).
- [RFC 7895: YANG module library](http://www.rfc-base.org/txt/rfc-7895.txt)
The aim is also to cover new features in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt)
Clixon has its own XML library designed for performance.
However, the following YANG syntax modules are not implemented:
`deviation`, `min/max-elements`, `unique`, and `action`.
Netconf
=======
Clixon 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)
Some updates are being made to RFC 6241 and RFC 6242.
- [RFC 6241: NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-6241.txt)
- [RFC 6242: Using the NETCONF Configuration Protocol over Secure Shell (SSH)](http://www.rfc-base.org/txt/rfc-6242.txt)
- [RFC 5277: NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
- [RFC 8341: Network Configuration Access Control Model](http://www.rfc-base.org/txt/rfc-8341.txt)
Clixon does not yet support the following netconf features:
@ -136,16 +134,18 @@ Clixon does not yet support the following netconf features:
Restconf
========
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
Clixon Restconf is a daemon based on FastCGI C-API. Instructions are available to
run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
The following features are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE
The following are not implemented
- stream notifications (RFC8040 sec 6)
- query parameters start-time and stop-time(RFC8040 section 4.9)
The following features are not implemented:
- PATCH
- query parameters (section 4.9)
- notifications (sec 6)
- schema resource
- query parameters other than start/stop-time.
See [more detailed instructions](apps/restconf/README.md).
@ -155,8 +155,6 @@ 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.
Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. Only a text datastore is implemented.
The datastore is primarily designed to be used by Clixon but can be used
separately.
@ -164,7 +162,6 @@ See [more detailed instructions](datastore/README.md).
Auth
====
Authentication is managed outside Clixon using SSH, SSL, Oauth2, etc.
For CLI, login is typically made via SSH. For netconf, SSH netconf
@ -179,10 +176,37 @@ The clients send the ID of the user using a "username" attribute with
the RPC calls to the backend. Note that the backend trusts the clients
so the clients can in principle fake a username.
There is an ongoing effort to implement authorization for Clixon
according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341), at
least a subset of the functionality. See more information here:
[NACM](README_NACM.md).
NACM
====
Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). It has limited functionality.
The support is as follows:
* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang)
* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted.
* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
* There are two [tests](test/README.md) using internal and external NACM config
* The backend provides a limited NACM support (when enabled) described below
NACM is implemented in the backend and a single access check is made
in `from_client_msg()` when an internal netconf RPC has
just been received and decoded. The code is in `nacm_access()`.
The functionality is as follows:
* Notification is not supported
* Groups are supported
* Rule-lists are supported
* Rules are supported as follows
* module-name: Only '*' supported
* access-operations: only '*' and 'exec' supported
* rpc-name: fully supported (eg edit-config/get-config, etc)
* action: fully supported (permit/deny)
The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
* admin: Full access
* limited: Read access (get and get-config)
* guest: No access
Runtime

View file

@ -1,34 +0,0 @@
# NACM
Clixon includes an experimental NACM implementation according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341).
The support is as follows:
* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang)
* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted.
* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
* There are two [tests](test/README.md) using internal and external NACM config
* The backend provides a limited NACM support (when enabled) described below
NACM functionality
==================
NACM is implemented in the backend and a single access check is made
in from_client_msg() when an internal netconf RPC has
just been received and decoded. The code is in nacm_access().
The functionality is as follows:
* Notification is not supported
* Groups are supported
* Rule-lists are supported
* Rules are supported as follows
* module-name: Only '*' supported
* access-operations: only '*' and 'exec' supported
* rpc-name: fully supported (eg edit-config/get-config, etc)
* action: fully supported (permit/deny)
The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
* admin: Full access
* limited: Read access (get and get-config)
* guest: No access

View file

@ -69,38 +69,9 @@
#include "backend_client.h"
#include "backend_handle.h"
/*! Add client notification subscription. Ie send notify to this client when event occurs
* @param[in] ce Client entry struct
* @param[in] stream Notification stream name
* @param[in] format How to display event (see enum format_enum)
* @param[in] filter Filter, what to display, eg xpath for format=xml, fnmatch
*
* @see backend_notify - where subscription is made and notify call is made
*/
static struct client_subscription *
client_subscription_add(struct client_entry *ce,
char *stream,
enum format_enum format,
char *filter)
{
struct client_subscription *su = NULL;
if ((su = malloc(sizeof(*su))) == NULL){
clicon_err(OE_PLUGIN, errno, "malloc");
goto done;
}
memset(su, 0, sizeof(*su));
su->su_stream = strdup(stream);
su->su_format = format;
su->su_filter = filter?strdup(filter):strdup("");
su->su_next = ce->ce_subscription;
ce->ce_subscription = su;
done:
return su;
}
static struct client_entry *
ce_find_bypid(struct client_entry *ce_list, int pid)
ce_find_bypid(struct client_entry *ce_list,
int pid)
{
struct client_entry *ce;
@ -110,47 +81,45 @@ ce_find_bypid(struct client_entry *ce_list, int pid)
return NULL;
}
static int
client_subscription_delete(struct client_entry *ce,
struct client_subscription *su0)
/*! Stream callback for netconf stream notification (RFC 5277)
* @param[in] h Clicon handle
* @param[in] op 0:event, 1:rm
* @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_ss_add
* @see stream_ss_add
*/
int
ce_event_cb(clicon_handle h,
int op,
cxobj *event,
void *arg)
{
struct client_subscription *su;
struct client_subscription **su_prev;
su_prev = &ce->ce_subscription; /* this points to stack and is not real backpointer */
for (su = *su_prev; su; su = su->su_next){
if (su == su0){
*su_prev = su->su_next;
free(su->su_stream);
if (su->su_filter)
free(su->su_filter);
free(su);
struct client_entry *ce = (struct client_entry *)arg;
clicon_debug(1, "%s op:%d", __FUNCTION__, op);
switch (op){
case 1:
/* Risk of recursion here */
if (ce->ce_s)
backend_client_rm(h, ce);
break;
default:
if (send_msg_notify_xml(ce->ce_s, event) < 0){
if (errno == ECONNRESET || errno == EPIPE){
clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
}
break;
}
su_prev = &su->su_next;
}
clicon_debug(1, "%s retval:0", __FUNCTION__);
return 0;
}
#ifdef notused /* xxx */
static struct client_subscription *
client_subscription_find(struct client_entry *ce, char *stream)
{
struct client_subscription *su = NULL;
for (su = ce->ce_subscription; su; su = su->su_next)
if (strcmp(su->su_stream, stream) == 0)
break;
return su;
}
#endif
/*! Remove client entry state
* Close down everything wrt clients (eg sockets, subscriptions)
* Finally actually remove client struct in handle
* @param[in] h Clicon handle
* @param[in] ce Client hadnle
* @param[in] ce Client handle
* @see backend_client_delete for actual deallocation of client entry struct
*/
int
@ -160,8 +129,10 @@ backend_client_rm(clicon_handle h,
struct client_entry *c;
struct client_entry *c0;
struct client_entry **ce_prev;
struct client_subscription *su;
clicon_debug(1, "%s", __FUNCTION__);
/* for all streams: XXX better to do it top-level? */
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
c0 = backend_client_list(h);
ce_prev = &c0; /* this points to stack and is not real backpointer */
for (c = *ce_prev; c; c = c->ce_next){
@ -171,12 +142,11 @@ backend_client_rm(clicon_handle h,
close(ce->ce_s);
ce->ce_s = 0;
}
while ((su = ce->ce_subscription) != NULL)
client_subscription_delete(ce, su);
break;
}
ce_prev = &c->ce_next;
}
return backend_client_delete(h, ce); /* actually purge it */
}
@ -258,6 +228,124 @@ from_client_get_config(clicon_handle h,
return retval;
}
/*! Get streams state according to RFC 8040 or RFC5277 common function
* @param[in] h Clicon handle
* @param[in] yspec Yang spec
* @param[in] xpath Xpath selection, not used but may be to filter early
* @param[in] module Name of yang module
* @param[in] top Top symbol, ie netconf or restconf-state
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
* @retval 0 OK
* @retval 1 Statedata callback failed
*/
static int
client_get_streams(clicon_handle h,
yang_spec *yspec,
char *xpath,
char *module,
char *top,
cxobj **xret)
{
int retval = -1;
yang_stmt *ystream = NULL; /* yang stream module */
yang_stmt *yns = NULL; /* yang namespace */
cxobj *x = NULL;
cbuf *cb = NULL;
if ((ystream = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL){
clicon_err(OE_YANG, 0, "%s yang module not found", module);
goto done;
}
if ((yns = yang_find((yang_node*)ystream, Y_NAMESPACE, NULL)) == NULL){
clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
goto done;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "clicon buffer");
goto done;
}
cprintf(cb,"<%s xmlns=\"%s\">", top, yns->ys_argument);
if (stream_get_xml(h, strcmp(top,"restconf-state")==0, cb) < 0)
goto done;
cprintf(cb,"</%s>", top);
if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
retval = 1;
goto done;
}
retval = netconf_trymerge(x, yspec, xret);
done:
if (cb)
cbuf_free(cb);
if (x)
xml_free(x);
return retval;
}
/*! Get system state-data, including streams and plugins
* @param[in] h Clicon handle
* @param[in] xpath Xpath selection, not used but may be to filter early
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
* @retval 0 OK
* @retval 1 Statedata callback failed
*/
static int
client_statedata(clicon_handle h,
char *xpath,
cxobj **xret)
{
int retval = -1;
cxobj **xvec = NULL;
size_t xlen;
int i;
yang_spec *yspec;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
(retval = client_get_streams(h, yspec, xpath, "ietf-netconf-notification", "netconf", xret)) != 0)
goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
(retval = client_get_streams(h, yspec, xpath, "ietf-restconf-monitoring", "restconf-state", xret)) != 0)
goto done;
if (clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895") &&
(retval = yang_modules_state_get(h, yspec, xret)) != 0)
goto done;
if ((retval = clixon_plugin_statedata(h, yspec, xpath, xret)) != 0)
goto done;
/* Code complex to filter out anything that is outside of xpath */
if (xpath_vec(*xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* If vectors are specified then mark the nodes found and
* then filter out everything else,
* otherwise return complete tree.
*/
if (xvec != NULL){
for (i=0; i<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Remove everything that is not marked */
if (!xml_flag(*xret, XML_FLAG_MARK))
if (xml_tree_prune_flagged_sub(*xret, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
/* reset flag */
if (xml_apply(*xret, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
retval = 0;
done:
if (xvec)
free(xvec);
return retval;
}
/*! Internal message: get
*
* @param[in] h Clicon handle
@ -270,18 +358,18 @@ from_client_get(clicon_handle h,
cxobj *xe,
cbuf *cbret)
{
int retval = -1;
cxobj *xfilter;
char *selector = "/";
cxobj *xret = NULL;
int ret;
cbuf *cbx = NULL; /* Assist cbuf */
int retval = -1;
cxobj *xfilter;
char *xpath = "/";
cxobj *xret = NULL;
int ret;
cbuf *cbx = NULL; /* Assist cbuf */
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((selector = xml_find_value(xfilter, "select"))==NULL)
selector="/";
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
/* Get config */
if (xmldb_get(h, "running", selector, 0, &xret) < 0){
if (xmldb_get(h, "running", xpath, 0, &xret) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done;
goto ok;
@ -289,7 +377,7 @@ from_client_get(clicon_handle h,
/* Get state data from plugins as defined by plugin_statedata(), if any */
assert(xret);
clicon_err_reset();
if ((ret = clixon_plugin_statedata(h, selector, &xret)) < 0)
if ((ret = client_statedata(h, xpath, &xret)) < 0)
goto done;
if (ret == 0){ /* OK */
cprintf(cbret, "<rpc-reply>");
@ -731,12 +819,13 @@ from_client_delete_config(clicon_handle h,
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK
* @retval -1 Error. Send error message back to client.
* @see RFC5277 2.1
* @example:
* <create-subscription>
* <stream>RESULT</stream> # If not present, events in the default NETCONF stream will be sent.
* <filter>XPATH-EXPR<(filter>
* <startTime/> # only for replay (NYI)
* <stopTime/> # only for replay (NYI)
* <filter type="xpath" select="XPATH-EXPR"/>
* <startTime></startTime>
* <stopTime></stopTime>
* </create-subscription>
*/
static int
@ -746,25 +835,66 @@ from_client_create_subscription(clicon_handle h,
cbuf *cbret)
{
char *stream = "NETCONF";
char *filter = NULL;
int retval = -1;
cxobj *x; /* Genereic xml tree */
cxobj *x; /* Generic xml tree */
cxobj *xfilter; /* Filter xml tree */
char *ftype;
char *starttime = NULL;
char *stoptime = NULL;
char *selector = NULL;
struct timeval start;
struct timeval stop;
if ((x = xpath_first(xe, "//stream")) != NULL)
stream = xml_find_value(x, "body");
if ((x = xpath_first(xe, "//filter")) != NULL){
if ((ftype = xml_find_value(x, "type")) != NULL){
if ((x = xpath_first(xe, "//stopTime")) != NULL){
if ((stoptime = xml_find_value(x, "body")) != NULL &&
str2time(stoptime, &stop) < 0){
if (netconf_bad_element(cbret, "application", "<bad-element>stopTime</bad-element>", "Expected timestamp") < 0)
goto done;
goto ok;
}
}
if ((x = xpath_first(xe, "//startTime")) != NULL){
if ((starttime = xml_find_value(x, "body")) != NULL &&
str2time(starttime, &start) < 0){
if (netconf_bad_element(cbret, "application", "<bad-element>startTime</bad-element>", "Expected timestamp") < 0)
goto done;
goto ok;
}
}
if ((xfilter = xpath_first(xe, "//filter")) != NULL){
if ((ftype = xml_find_value(xfilter, "type")) != NULL){
/* Only accept xpath as filter type */
if (strcmp(ftype, "xpath") != 0){
if (netconf_operation_failed(cbret, "application", "Only xpath filter type supported")< 0)
goto done;
goto ok;
}
if ((selector = xml_find_value(xfilter, "select")) == NULL)
goto done;
}
}
if (client_subscription_add(ce, stream, FORMAT_XML, filter) == NULL)
if ((stream_find(h, stream)) == NULL){
if (netconf_invalid_value(cbret, "application", "No such stream") < 0)
goto done;
goto ok;
}
/* Add subscriber to stream - to make notifications for this client */
if (stream_ss_add(h, stream, selector,
starttime?&start:NULL, stoptime?&stop:NULL,
ce_event_cb, (void*)ce) < 0)
goto done;
/* Replay of this stream to specific subscription according to start and
* stop (if present).
* RFC 5277: If <startTime> is not present, this is not a replay
* subscription.
* Schedule the replay to occur right after this RPC completes, eg "now"
*/
if (starttime){
if (stream_replay_trigger(h, stream, ce_event_cb, (void*)ce) < 0)
goto done;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
@ -987,13 +1117,7 @@ nacm_access(clicon_handle h,
if (username == NULL)
goto step10;
/* User's group */
if (xpath_vec(xacm,
#ifdef COMPAT_XSL
"groups/group[user-name=%s]",
#else
"groups/group[user-name='%s']",
#endif
&gvec, &glen, username) < 0)
if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
goto done;
/* 5. If no groups are found, continue with step 10. */
if (glen == 0)
@ -1010,13 +1134,7 @@ nacm_access(clicon_handle h,
for (j=0; j<glen; j++){
char *gname;
gname = xml_find_body(gvec[j], "name");
if (xpath_first(xrlist,
#ifdef COMPAT_XSL
".[group=%s]",
#else
".[group='%s']",
#endif
gname)!=NULL)
if (xpath_first(xrlist, ".[group='%s']", gname)!=NULL)
break; /* found */
}
if (j==glen) /* not found */
@ -1170,6 +1288,7 @@ from_client_msg(clicon_handle h,
}
else if (strcmp(name, "close-session") == 0){
xmldb_unlock_all(h, pid);
stream_ss_delete_all(h, ce_event_cb, (void*)ce);
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
}
else if (strcmp(name, "kill-session") == 0){

View file

@ -52,19 +52,8 @@ struct client_entry{
int ce_pid; /* Process id */
int ce_uid; /* User id of calling process */
clicon_handle ce_handle; /* clicon config handle (all clients have same?) */
struct client_subscription *ce_subscription; /* notification subscriptions */
};
/* Notification subscription info
* @see subscription in config_handle.c
*/
struct client_subscription{
struct client_subscription *su_next;
int su_s; /* stream socket */
enum format_enum su_format; /* format of notification stream */
char *su_stream;
char *su_filter;
};
/*
* Prototypes

View file

@ -73,19 +73,27 @@
#include "backend_handle.h"
/* Command line options to be passed to getopt(3) */
#define BACKEND_OPTS "hD:f:d:b:Fzu:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */
#define BACKEND_OPTS "hD:f:l:d:b:Fza:u:P:1s:c:g:y:x:" /* substitute s: for IRCc:r */
#define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
/*! Terminate. Cannot use h after this */
static int
backend_terminate(clicon_handle h)
{
yang_spec *yspec;
char *pidfile = clicon_backend_pidfile(h);
char *sockpath = clicon_sock(h);
yang_spec *yspec;
char *pidfile = clicon_backend_pidfile(h);
char *sockpath = clicon_sock(h);
cxobj *x;
clicon_debug(1, "%s", __FUNCTION__);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
if ((yspec = clicon_config_yang(h)) != NULL)
yspec_free(yspec);
if ((x = clicon_conf_xml(h)) != NULL)
xml_free(x);
stream_publish_exit();
clixon_plugin_exit(h);
/* Delete all backend plugin RPC callbacks */
rpc_callback_delete_all();
@ -94,10 +102,10 @@ backend_terminate(clicon_handle h)
if (sockpath)
unlink(sockpath);
xmldb_plugin_unload(h); /* unload storage plugin */
backend_handle_exit(h); /* Cannot use h after this */
backend_handle_exit(h); /* Also deletes streams. Cannot use h after this. */
event_exit();
clicon_log_register_callback(NULL, NULL);
clicon_debug(1, "%s done", __FUNCTION__);
clicon_log_exit();
return 0;
}
@ -125,23 +133,26 @@ usage(clicon_handle h,
char *confpid = clicon_backend_pidfile(h);
char *group = clicon_sock_group(h);
fprintf(stderr, "usage:%s\n"
fprintf(stderr, "usage:%s <options>*\n"
"where options are\n"
" -h\t\tHelp\n"
" -D <level>\tDebug level\n"
" -f <file>\tCLICON config file (mandatory)\n"
" -d <dir>\tSpecify backend plugin directory (default: %s)\n"
" -b <dir>\tSpecify XMLDB database directory\n"
" -z\t\tKill other config daemon and exit\n"
" -F\t\tRun in foreground, do not run as daemon\n"
" -1\t\tRun once and then quit (dont wait for events)\n"
" -u <path>\tConfig UNIX domain path / ip address (default: %s)\n"
" -P <file>\tPid filename (default: %s)\n"
" -s <mode>\tSpecify backend startup mode: none|startup|running|init (replaces -IRCr\n"
" -c <file>\tLoad extra xml configuration, but don't commit.\n"
" -g <group>\tClient membership required to this group (default: %s)\n"
" -y <file>\tOverride yang spec file (dont include .yang suffix)\n"
" -x <plugin>\tXMLDB plugin\n",
"\t-h\t\tHelp\n"
"\t-D <level>\tDebug level\n"
"\t-f <file>\tCLICON config file\n"
"\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
"\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
"\t-b <dir>\tSpecify XMLDB database directory\n"
"\t-F\t\tRun in foreground, do not run as daemon\n"
"\t-z\t\tKill other config daemon and exit\n"
"\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)(default: %s)\n"
"\t-P <file>\tPid filename (default: %s)\n"
"\t-1\t\tRun once and then quit (dont wait for events)\n"
"\t-s <mode>\tSpecify backend startup mode: none|startup|running|init)\n"
"\t-c <file>\tLoad extra xml configuration, but don't commit.\n"
"\t-g <group>\tClient membership required to this group (default: %s)\n"
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-x <plugin>\tXMLDB plugin\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
@ -185,7 +196,6 @@ db_merge(clicon_handle h,
return retval;
}
/*! Create backend server socket and register callback
*/
static int
@ -205,45 +215,6 @@ server_socket(clicon_handle h)
return ss;
}
/*! Callback for CLICON log events
* If you make a subscription to CLICON stream, this function is called for every
* log event.
*/
static int
backend_log_cb(int level,
char *msg,
void *arg)
{
int retval = -1;
size_t n;
char *ptr;
char *nptr;
char *newmsg = NULL;
/* backend_notify() will go through all clients and see if any has
registered "CLICON", and if so make a clicon_proto notify message to
those clients.
Sanitize '%' into "%%" to prevent segvfaults in vsnprintf later.
At this stage all formatting is already done */
n = 0;
for(ptr=msg; *ptr; ptr++)
if (*ptr == '%')
n++;
if ((newmsg = malloc(strlen(msg) + n + 1)) == NULL) {
clicon_err(OE_UNIX, errno, "malloc");
return -1;
}
for(ptr=msg, nptr=newmsg; *ptr; ptr++) {
*nptr++ = *ptr;
if (*ptr == '%')
*nptr++ = '%';
}
retval = backend_notify(arg, "CLICON", level, newmsg);
free(newmsg);
return retval;
}
/*! Call plugin_start with -- user options */
static int
plugin_start_useroptions(clicon_handle h,
@ -293,7 +264,7 @@ nacm_load_external(clicon_handle h)
}
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_parse(h, CLIXON_DATADIR, "ietf-netconf-acm", NULL, yspec) < 0)
if (yang_parse(h, NULL, "ietf-netconf-acm", CLIXON_DATADIR, NULL, yspec, NULL) < 0)
goto done;
fd = fileno(f);
/* Read configfile */
@ -553,9 +524,13 @@ main(int argc,
int xml_pretty;
char *xml_format;
char *nacm_mode;
int logdst = CLICON_LOG_SYSLOG|CLICON_LOG_STDERR;
yang_spec *yspec = NULL;
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
char *yang_filename = NULL;
/* In the startup, logs to stderr & syslog and debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR|CLICON_LOG_SYSLOG);
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
/* Initiate CLICON handle */
if ((h = backend_handle_init()) == NULL)
return -1;
@ -571,7 +546,6 @@ main(int argc,
optind = 1;
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
switch (c) {
case '?':
case 'h':
/* Defer the call to usage() to later. Reason is that for helpful
text messages, default dirs, etc, are not set until later.
@ -589,6 +563,14 @@ main(int argc,
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]);
if (logdst == CLICON_LOG_FILE &&
strlen(optarg)>1 &&
clicon_log_file(optarg+1) < 0)
goto done;
break;
}
/*
* Here we have the debug flag settings, use that.
@ -597,15 +579,20 @@ main(int argc,
* XXX: if started in a start-daemon script, there will be irritating
* double syslogs until fork below.
*/
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL);
/* Create configure yang-spec */
if ((yspecfg = yspec_new()) == NULL)
goto done;
/* Find and read configfile */
if (clicon_options_main(h) < 0){
if (clicon_options_main(h, yspecfg) < 0){
if (help)
usage(h, argv[0]);
return -1;
}
clicon_config_yang_set(h, yspecfg);
/* External NACM file? */
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode && strcmp(nacm_mode, "external") == 0)
@ -617,8 +604,10 @@ main(int argc,
optind = 1;
while ((c = getopt(argc, argv, BACKEND_OPTS)) != -1)
switch (c) {
case 'h' : /* help */
case 'D' : /* debug */
case 'f': /* config file */
case 'l' :
break; /* see above */
case 'd': /* Plugin directory */
if (!strlen(optarg))
@ -633,12 +622,12 @@ main(int argc,
case 'F' : /* foreground */
foreground = 1;
break;
case '1' : /* Quit after reading database once - dont wait for events */
once = 1;
break;
case 'z': /* Zap other process */
zap++;
break;
case 'a': /* internal backend socket address family */
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
break;
case 'u': /* config unix domain path / ip address */
if (!strlen(optarg))
usage(h, argv[0]);
@ -647,6 +636,9 @@ main(int argc,
case 'P': /* pidfile */
clicon_option_str_set(h, "CLICON_BACKEND_PIDFILE", optarg);
break;
case '1' : /* Quit after reading database once - dont wait for events */
once = 1;
break;
case 's' : /* startup mode */
clicon_option_str_set(h, "CLICON_STARTUP_MODE", optarg);
if (clicon_startup_mode(h) < 0){
@ -660,8 +652,8 @@ main(int argc,
case 'g': /* config socket group */
clicon_option_str_set(h, "CLICON_SOCK_GROUP", optarg);
break;
case 'y' :{ /* Override yang module or absolute filename */
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
case 'y' :{ /* Load yang spec file (override yang main module) */
yang_filename = optarg;
break;
}
case 'x' :{ /* xmldb plugin */
@ -676,6 +668,8 @@ main(int argc,
argc -= optind;
argv += optind;
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
/* Defer: Wait to the last minute to print help message */
if (help)
usage(h, argv[0]);
@ -735,6 +729,13 @@ main(int argc,
return -1;
}
/* Publish stream on pubsub channels.
* CLICON_STREAM_PUB should be set to URL to where streams are published
* and configure should be run with --enable-publish
*/
if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
stream_publish_init() < 0)
goto done;
if ((xmldb_plugin = clicon_xmldb_plugin(h)) == NULL){
clicon_log(LOG_ERR, "No xmldb plugin given (specify option CLICON_XMLDB_PLUGIN).\n");
goto done;
@ -744,10 +745,37 @@ main(int argc,
/* Connect to plugin to get a handle */
if (xmldb_connect(h) < 0)
goto done;
/* Parse db spec file */
if (yang_spec_main(h) == NULL)
goto done;
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
/* Load main application yang specification either module or specific file
* If -y <file> is given, it overrides main module */
if (yang_filename){
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
goto done;
}
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
clicon_yang_dir(h),
clicon_yang_module_revision(h),
yspec, NULL) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
/* Add netconf yang spec, used by netconf client and as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
/* Load yang Restconf stream discovery */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
/* Load yang Netconf stream discovery */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
/* Set options: database dir and yangspec (could be hidden in connect?)*/
if (xmldb_setopt(h, "dbdir", clicon_xmldb_dir(h)) < 0)
goto done;
@ -815,9 +843,6 @@ main(int argc,
if ((pid = pidfile_write(pidfile)) < 0)
goto done;
/* Register log notifications */
if (clicon_log_register_callback(backend_log_cb, h) < 0)
goto done;
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, backend_sig_term, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
@ -835,6 +860,8 @@ main(int argc,
if (debug)
clicon_option_dump(h, debug);
if (stream_timer_setup(0, h) < 0)
goto done;
if (event_loop() < 0)
goto done;
retval = 0;

View file

@ -100,7 +100,7 @@ clixon_plugin_reset(clicon_handle h,
if ((resetfn = cp->cp_api.ca_reset) == NULL)
continue;
if ((retval = resetfn(h, db)) < 0) {
clicon_debug(1, "plugin_start() failed\n");
clicon_debug(1, "plugin_start() failed");
return -1;
}
break;
@ -112,6 +112,7 @@ clixon_plugin_reset(clicon_handle h,
* This is internal system call, plugin is invoked (does not call) this function
* Backend plugins can register
* @param[in] h clicon handle
* @param[in] yspec Yang spec
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in,out] xtop State XML tree is merged with existing tree.
* @retval -1 Error
@ -120,29 +121,17 @@ clixon_plugin_reset(clicon_handle h,
* @note xtop can be replaced
*/
int
clixon_plugin_statedata(clicon_handle h,
char *xpath,
cxobj **xtop)
clixon_plugin_statedata(clicon_handle h,
yang_spec *yspec,
char *xpath,
cxobj **xret)
{
int retval = -1;
int i;
cxobj *x = NULL;
yang_spec *yspec;
cxobj **xvec = NULL;
size_t xlen;
cxobj *xc;
int retval = -1;
int ret;
cxobj *x = NULL;
clixon_plugin *cp = NULL;
plgstatedata_t *fn; /* Plugin statedata fn */
char *reason = NULL;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if (*xtop==NULL){
clicon_err(OE_CFG, ENOENT, "XML tree expected");
goto done;
}
while ((cp = clixon_plugin_each(h, cp)) != NULL) {
if ((fn = cp->cp_api.ca_statedata) == NULL)
continue;
@ -152,50 +141,19 @@ clixon_plugin_statedata(clicon_handle h,
retval = 1;
goto done; /* Dont quit here on user callbacks */
}
if (xml_merge(*xtop, x, yspec, &reason) < 0)
if ((ret = netconf_trymerge(x, yspec, xret)) != 0){
retval = ret;
goto done;
if (reason){
while ((xc = xml_child_i(*xtop, 0)) != NULL)
xml_purge(xc);
clicon_log(LOG_NOTICE, "%s: Plugin '%s' state callback failed",
__FUNCTION__, cp->cp_name);
if (netconf_operation_failed_xml(xtop, "rpc", reason)< 0)
goto done;
goto ok;
}
if (x){
xml_free(x);
x = NULL;
}
}
/* Code complex to filter out anything that is outside of xpath */
if (xpath_vec(*xtop, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* If vectors are specified then mark the nodes found and
* then filter out everything else,
* otherwise return complete tree.
*/
if (xvec != NULL){
for (i=0; i<xlen; i++)
xml_flag_set(xvec[i], XML_FLAG_MARK);
}
/* Remove everything that is not marked */
if (!xml_flag(*xtop, XML_FLAG_MARK))
if (xml_tree_prune_flagged_sub(*xtop, XML_FLAG_MARK, 1, NULL) < 0)
goto done;
/* reset flag */
if (xml_apply(*xtop, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
ok:
retval = 0;
done:
if (reason)
free(reason);
if (x)
xml_free(x);
if (xvec)
free(xvec);
return retval;
}
@ -259,7 +217,7 @@ plugin_transaction_begin(clicon_handle h,
clicon_log(LOG_NOTICE, "%s: Plugin '%s' transaction_begin callback does not make clicon_err call on error",
__FUNCTION__, cp->cp_name);
break;
break;
}
}
return retval;
@ -357,7 +315,7 @@ plugin_transaction_revert(clicon_handle h,
while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) {
if ((fn = cp->cp_api.ca_trans_commit) == NULL)
continue;
if ((retval = fn(h, (transaction_data)td)) < 0){
if ((retval = fn(h, (transaction_data)&tr)) < 0){
clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_commit revert callback failed",
__FUNCTION__, cp->cp_name);
break;

View file

@ -71,7 +71,7 @@ int backend_plugin_initiate(clicon_handle h);
int clixon_plugin_reset(clicon_handle h, char *db);
int clixon_plugin_statedata(clicon_handle h, char *xpath, cxobj **xtop);
int clixon_plugin_statedata(clicon_handle h, yang_spec *yspec, char *xpath, cxobj **xtop);
transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *);

View file

@ -74,7 +74,8 @@
#include "backend_handle.h"
static int
config_socket_init_ipv4(clicon_handle h, char *dst)
config_socket_init_ipv4(clicon_handle h,
char *dst)
{
int s;
struct sockaddr_in addr;
@ -116,7 +117,8 @@ config_socket_init_ipv4(clicon_handle h, char *dst)
* and group according to CLICON_SOCK_GROUP option.
*/
static int
config_socket_init_unix(clicon_handle h, char *sock)
config_socket_init_unix(clicon_handle h,
char *sock)
{
int s;
struct sockaddr_un addr;
@ -197,7 +199,7 @@ backend_socket_init(clicon_handle h)
*/
int
backend_accept_client(int fd,
void *arg)
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;

View file

@ -86,10 +86,11 @@ struct backend_handle {
int bh_magic; /* magic (HDR)*/
clicon_hash_t *bh_copt; /* clicon option list (HDR) */
clicon_hash_t *bh_data; /* internal clicon data (HDR) */
event_stream_t *bh_stream; /* notification streams, see clixon_stream.[ch] */
/* ------ end of common handle ------ */
struct client_entry *bh_ce_list; /* The client list */
int bh_ce_nr; /* Number of clients, just increment */
struct handle_subscription *bh_subscription; /* Event subscription list */
cxobj *bh_nacm; /* NACM external struct */
};
@ -103,6 +104,7 @@ backend_handle_init(void)
/*! Deallocates a backend handle, including all client structs
* @Note: handle 'h' cannot be used in calls after this
* @see backend_client_rm
*/
int
backend_handle_exit(clicon_handle h)
@ -110,164 +112,20 @@ backend_handle_exit(clicon_handle h)
struct backend_handle *bh = handle(h);
struct client_entry *ce;
/* only delete client structs, not close sockets, etc, see backend_client_rm */
while ((ce = backend_client_list(h)) != NULL)
/* only delete client structs, not close sockets, etc, see backend_client_rm WHY NOT? */
while ((ce = backend_client_list(h)) != NULL){
if (ce->ce_s){
close(ce->ce_s);
ce->ce_s = 0;
}
backend_client_delete(h, ce);
}
if (bh->bh_nacm)
xml_free(bh->bh_nacm);
clicon_handle_exit(h); /* frees h and options */
clicon_handle_exit(h); /* frees h and options (and streams) */
return 0;
}
/*! Notify event and distribute to all registered clients
*
* @param[in] h Clicon handle
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
* @param[in] level Event level (not used yet)
* @param[in] event Actual message as text format
*
* Stream is a string used to qualify the event-stream. Distribute the
* event to all clients registered to this backend.
* XXX: event-log NYI.
* @see also subscription_add()
* @see also backend_notify_xml()
*/
int
backend_notify(clicon_handle h,
char *stream,
int level,
char *event)
{
struct client_entry *ce;
struct client_entry *ce_next;
struct client_subscription *su;
struct handle_subscription *hs;
int retval = -1;
clicon_debug(2, "%s %s", __FUNCTION__, stream);
/* First thru all clients(sessions), and all subscriptions and find matches */
for (ce = backend_client_list(h); ce; ce = ce_next){
ce_next = ce->ce_next;
for (su = ce->ce_subscription; su; su = su->su_next)
if (strcmp(su->su_stream, stream) == 0){
if (strlen(su->su_filter)==0 || fnmatch(su->su_filter, event, 0) == 0){
if (send_msg_notify(ce->ce_s, level, event) < 0){
if (errno == ECONNRESET || errno == EPIPE){
clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0
/* We should remove here but removal is not possible
from a client since backend_client is not linked.
Maybe we should add it to the plugin, but it feels
"safe" that you cant remove a client.
Instead, the client is (hopefully) removed elsewhere?
*/
backend_client_rm(h, ce);
#endif
break;
}
goto done;
}
}
}
}
/* Then go thru all global (handle) subscriptions and find matches */
hs = NULL;
while ((hs = subscription_each(h, hs)) != NULL){
if (hs->hs_format != FORMAT_TEXT)
continue;
if (strcmp(hs->hs_stream, stream))
continue;
if (hs->hs_filter==NULL ||
strlen(hs->hs_filter)==0 ||
fnmatch(hs->hs_filter, event, 0) == 0)
if ((*hs->hs_fn)(h, event, hs->hs_arg) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Notify event and distribute to all registered clients
*
* @param[in] h Clicon handle
* @param[in] stream Name of event stream. CLICON is predefined as LOG stream
* @param[in] level Event level (not used yet)
* @param[in] x Actual message as xml tree
*
* Stream is a string used to qualify the event-stream. Distribute the
* event to all clients registered to this backend.
* XXX: event-log NYI.
* @see also subscription_add()
* @see also backend_notify()
*/
int
backend_notify_xml(clicon_handle h,
char *stream,
int level,
cxobj *x)
{
struct client_entry *ce;
struct client_entry *ce_next;
struct client_subscription *su;
int retval = -1;
cbuf *cb = NULL;
struct handle_subscription *hs;
clicon_debug(1, "%s %s", __FUNCTION__, stream);
/* Now go thru all clients(sessions), and all subscriptions and find matches */
for (ce = backend_client_list(h); ce; ce = ce_next){
ce_next = ce->ce_next;
for (su = ce->ce_subscription; su; su = su->su_next)
if (strcmp(su->su_stream, stream) == 0){
if (strlen(su->su_filter)==0 || xpath_first(x, "%s", su->su_filter) != NULL){
if (cb==NULL){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, x, 0, 0) < 0)
goto done;
}
if (send_msg_notify(ce->ce_s, level, cbuf_get(cb)) < 0){
if (errno == ECONNRESET || errno == EPIPE){
clicon_log(LOG_WARNING, "client %d reset", ce->ce_nr);
#if 0
/* We should remove here but removal is not possible
from a client since backend_client is not linked.
Maybe we should add it to the plugin, but it feels
"safe" that you cant remove a client.
Instead, the client is (hopefully) removed elsewhere?
*/
backend_client_rm(h, ce);
#endif
break;
}
goto done;
}
}
}
}
/* Then go thru all global (handle) subscriptions and find matches */
hs = NULL;
while ((hs = subscription_each(h, hs)) != NULL){
if (hs->hs_format != FORMAT_XML)
continue;
if (strcmp(hs->hs_stream, stream))
continue;
if (strlen(hs->hs_filter)==0 || xpath_first(x, "%s", hs->hs_filter) != NULL){
if ((*hs->hs_fn)(h, x, hs->hs_arg) < 0)
goto done;
}
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Add new client, typically frontend such as cli, netconf, restconf
* @param[in] h Clicon handle
* @param[in] addr Address of client
@ -330,111 +188,6 @@ backend_client_delete(clicon_handle h,
return 0;
}
/*! Add subscription given stream name, callback and argument
* @param[in] h Clicon handle
* @param[in] stream Name of event stream
* @param[in] format Expected format of event, eg text or xml
* @param[in] filter Filter to match event, depends on format, eg xpath for xml
* @param[in] fn Callback when event occurs
* @param[in] arg Argument to use with callback. Also handle when deleting
* Note that arg is not a real handle.
* @see subscription_delete
* @see subscription_each
*/
struct handle_subscription *
subscription_add(clicon_handle h,
char *stream,
enum format_enum format,
char *filter,
subscription_fn_t fn,
void *arg)
{
struct backend_handle *bh = handle(h);
struct handle_subscription *hs = NULL;
if ((hs = malloc(sizeof(*hs))) == NULL){
clicon_err(OE_PLUGIN, errno, "malloc");
goto done;
}
memset(hs, 0, sizeof(*hs));
hs->hs_stream = strdup(stream);
hs->hs_format = format;
hs->hs_filter = filter?strdup(filter):NULL;
hs->hs_next = bh->bh_subscription;
hs->hs_fn = fn;
hs->hs_arg = arg;
bh->bh_subscription = hs;
done:
return hs;
}
/*! Delete subscription given stream name, callback and argument
* @param[in] h Clicon handle
* @param[in] stream Name of event stream
* @param[in] fn Callback when event occurs
* @param[in] arg Argument to use with callback and handle
* Note that arg is not a real handle.
* @see subscription_add
* @see subscription_each
*/
int
subscription_delete(clicon_handle h,
char *stream,
subscription_fn_t fn,
void *arg)
{
struct backend_handle *bh = handle(h);
struct handle_subscription *hs;
struct handle_subscription **hs_prev;
hs_prev = &bh->bh_subscription; /* this points to stack and is not real backpointer */
for (hs = *hs_prev; hs; hs = hs->hs_next){
/* XXX arg == hs->hs_arg */
if (strcmp(hs->hs_stream, stream)==0 && hs->hs_fn == fn){
*hs_prev = hs->hs_next;
free(hs->hs_stream);
if (hs->hs_filter)
free(hs->hs_filter);
if (hs->hs_arg)
free(hs->hs_arg);
free(hs);
break;
}
hs_prev = &hs->hs_next;
}
return 0;
}
/*! Iterator over subscriptions
*
* NOTE: Never manipulate the child-list during operation or using the
* same object recursively, the function uses an internal field to remember the
* index used. It works as long as the same object is not iterated concurrently.
*
* @param[in] h clicon handle
* @param[in] hprev iterator, initialize with NULL
* @code
* clicon_handle h;
* struct handle_subscription *hs = NULL;
* while ((hs = subscription_each(h, hs)) != NULL) {
* ...
* }
* @endcode
*/
struct handle_subscription *
subscription_each(clicon_handle h,
struct handle_subscription *hprev)
{
struct backend_handle *bh = handle(h);
struct handle_subscription *hs = NULL;
if (hprev)
hs = hprev->hs_next;
else
hs = bh->bh_subscription;
return hs;
}
int
backend_nacm_list_set(clicon_handle h,
cxobj *xnacm)

View file

@ -43,37 +43,9 @@
/*
* Types
*/
/* subscription callback */
typedef int (*subscription_fn_t)(clicon_handle, void *filter, void *arg);
/* Notification subscription info
* @see client_subscription in config_client.h
*/
struct handle_subscription{
struct handle_subscription *hs_next;
enum format_enum hs_format; /* format (enum format_enum) XXX not needed? */
char *hs_stream; /* name of notify stream */
char *hs_filter; /* filter, if format=xml: xpath, if text: fnmatch */
subscription_fn_t hs_fn; /* Callback when event occurs */
void *hs_arg; /* Callback argument */
};
/*
* Prototypes
*/
/* Log for netconf notify function (config_client.c) */
int backend_notify(clicon_handle h, char *stream, int level, char *txt);
int backend_notify_xml(clicon_handle h, char *stream, int level, cxobj *x);
struct handle_subscription *subscription_add(clicon_handle h, char *stream,
enum format_enum format, char *filter,
subscription_fn_t fn, void *arg);
int subscription_delete(clicon_handle h, char *stream,
subscription_fn_t fn, void *arg);
struct handle_subscription *subscription_each(clicon_handle h,
struct handle_subscription *hprev);
#endif /* _CLIXON_BACKEND_HANDLE_H_ */

View file

@ -1038,79 +1038,6 @@ cli_notify(clicon_handle h,
return retval;
}
/* Backward compatible Set if you want to enable "v" cli callback functions,
* such as cli_setv()
* This was obsoleted in 3.7.
* @see include/clixon_custom.h
*/
#ifdef COMPAT_CLIV
int cli_setv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_set(h, vars, argv);
}
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_merge(h, vars, argv);
}
int cli_delv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_del(h, vars, argv);
}
int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_debug_cli(h, vars, argv);
}
int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_debug_backend(h, vars, argv);
}
int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_set_mode(h, vars, argv);
}
int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_start_shell(h, vars, argv);
}
int cli_quitv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_quit(h, vars, argv);
}
int cli_commitv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_commit(h, vars, argv);
}
int cli_validatev(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_validate(h, vars, argv);
}
int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv)
{
return compare_dbs(h, vars, argv);
}
int load_config_filev(clicon_handle h, cvec *vars, cvec *argv)
{
return load_config_file(h, vars, argv);
}
int save_config_filev(clicon_handle h, cvec *vars, cvec *argv)
{
return save_config_file(h, vars, argv);
}
int delete_allv(clicon_handle h, cvec *vars, cvec *argv)
{
return delete_all(h, vars, argv);
}
int discard_changesv(clicon_handle h, cvec *vars, cvec *argv)
{
return discard_changes(h, vars, argv);
}
int cli_notifyv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_notify(h, vars, argv);
}
#endif /* COMPAT_CLIV */
/*! Lock database
*
* @param[in] h Clicon handle
@ -1249,23 +1176,6 @@ cli_copy_config(clicon_handle h,
for (i=0; i<strlen(xpath); i++){
if (xpath[i] == '%')
j++;
#ifdef COMPAT_XSL
/* This is a horrible kludge due to:
* (1) old xpath implementation wrongly did: a[b=x] instead of a[b='x']
* (2) cli_copy_config has as 2nd argument such an xpath provided by user.
*/
if (j==2){
int k;
if ((xpath[i-1] == '\'' || xpath[i-1] == '\"') &&
(xpath[i+2] == '\'' || xpath[i+2] == '\"')){
for (k=i-1;k<i+2;k++)
xpath[k] = xpath[k+1];
for (k=i+1;k<strlen(xpath)+1;k++)
xpath[k] = xpath[k+2];
i-=1;
}
}
#endif
}
if (j != 2){
clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have two '%%'", xpath);

View file

@ -232,19 +232,21 @@ yang2cli_var_sub(clicon_handle h,
cprintf(cb, ">");
if (helptext)
cprintf(cb, "(\"%s\")", helptext);
cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr);
if ((ybaseref = yang_find((yang_node*)ytype, Y_BASE, NULL)) != NULL &&
(ybaseid = yang_find_identity(ys, ybaseref->ys_argument)) != NULL){
i = 0;
while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){
if (i++)
cprintf(cb, "|");
name = strdup(cv_name_get(cv));
if ((id=strchr(name, ':')) != NULL)
*id = '\0';
cprintf(cb, "%s:%s", name, id+1);
if (name)
free(name);
if (cvec_len(ybaseid->ys_cvec) > 0){
cprintf(cb, "|<%s:%s choice:", ys->ys_argument, cvtypestr);
i = 0;
while ((cv = cvec_each(ybaseid->ys_cvec, cv)) != NULL){
if (i++)
cprintf(cb, "|");
name = strdup(cv_name_get(cv));
if ((id=strchr(name, ':')) != NULL)
*id = '\0';
cprintf(cb, "%s:%s", name, id+1);
if (name)
free(name);
}
}
}
}
@ -792,7 +794,7 @@ yang2cli(clicon_handle h,
if (yang2cli_stmt(h, ymod, cbuf, gt, 0) < 0)
goto done;
}
clicon_debug(1, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
clicon_debug(0, "%s: buf\n%s\n", __FUNCTION__, cbuf_get(cbuf));
/* Parse the buffer using cligen parser. XXX why this?*/
if ((globals = cvec_new(0)) == NULL)
goto done;

View file

@ -76,14 +76,14 @@
* @see struct clicon_handle, struct backend_handle
*/
struct cli_handle {
int cl_magic; /* magic (HDR)*/
clicon_hash_t *cl_copt; /* clicon option list (HDR) */
clicon_hash_t *cl_data; /* internal clicon data (HDR) */
int cl_magic; /* magic (HDR)*/
clicon_hash_t *cl_copt; /* clicon option list (HDR) */
clicon_hash_t *cl_data; /* internal clicon data (HDR) */
event_stream_t *cl_stream; /* notification streams, see clixon_stream.[ch] */
/* ------ end of common handle ------ */
cligen_handle cl_cligen; /* cligen handle */
cli_syntax_t *cl_stx; /* syntax structure */
cligen_handle cl_cligen; /* cligen handle */
cli_syntax_t *cl_stx; /* syntax structure */
};
/*! Return a clicon handle for other CLICON API calls

View file

@ -71,19 +71,27 @@
#include "cli_handle.h"
/* Command line options to be passed to getopt(3) */
#define CLI_OPTS "hD:f:xl:F:1u:d:m:qpGLy:c:U:"
#define CLI_OPTS "hD:f:xl:F:1a:u:d:m:qpGLy:c:U:"
#define CLI_LOGFILE "/tmp/clixon_cli.log"
/*! terminate cli application */
static int
cli_terminate(clicon_handle h)
{
yang_spec *yspec;
yang_spec *yspec;
cxobj *x;
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
if ((yspec = clicon_config_yang(h)) != NULL)
yspec_free(yspec);
if ((x = clicon_conf_xml(h)) != NULL)
xml_free(x);
cli_plugin_finish(h);
cli_handle_exit(h);
clicon_log_exit();
return 0;
}
@ -197,31 +205,30 @@ static void
usage(clicon_handle h,
char *argv0)
{
char *confsock = clicon_sock(h);
char *plgdir = clicon_cli_dir(h);
fprintf(stderr, "usage:%s [options] [commands]\n"
"where commands is a CLI command or options passed to the main plugin\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D <level> \tDebug\n"
"\t-D <level> \tDebug level\n"
"\t-f <file> \tConfig-file (mandatory)\n"
"\t-x\t\tDump configuration file as XML on stdout (migration utility)\n"
"\t-F <file> \tRead commands from file (default stdin)\n"
"\t-1\t\tDo not enter interactive mode\n"
"\t-u <sockpath>\tconfig UNIX domain path (default: %s)\n"
"\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n"
"\t-d <dir>\tSpecify plugin directory (default: %s)\n"
"\t-m <mode>\tSpecify plugin syntax mode\n"
"\t-q \t\tQuiet mode, dont print greetings or prompt, terminate on ctrl-C\n"
"\t-p \t\tPrint database yang specification\n"
"\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n"
"\t-L \t\tDebug print dynamic CLI syntax including completions and expansions\n"
"\t-l <s|e|o> \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n"
"\t-l <s|e|o|f<file>> \tLog on (s)yslog, std(e)rr, std(o)ut or (f)ile (stderr is default)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n"
"\t-c <file>\tSpecify cli spec file.\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n",
argv0,
confsock ? confsock : "none",
plgdir ? plgdir : "none"
);
exit(1);
@ -248,7 +255,10 @@ main(int argc, char **argv)
char *restarg = NULL; /* what remains after options */
int dump_configfile_xml = 0;
yang_spec *yspec;
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
struct passwd *pw;
char *yang_filename = NULL;
yang_stmt *ymod = NULL; /* Main module */
/* Defaults */
once = 0;
@ -297,21 +307,14 @@ main(int argc, char **argv)
case 'x': /* dump config file as xml (migration from .conf file)*/
dump_configfile_xml++;
break;
case 'l': /* Log destination: s|e|o */
switch (optarg[0]){
case 's':
logdst = CLICON_LOG_SYSLOG;
break;
case 'e':
logdst = CLICON_LOG_STDERR;
break;
case 'o':
logdst = CLICON_LOG_STDOUT;
break;
default:
usage(h, argv[0]);
}
break;
case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]);
if (logdst == CLICON_LOG_FILE &&
strlen(optarg)>1 &&
clicon_log_file(optarg+1) < 0)
goto done;
break;
}
/*
* Logs, error and debug to stderr or syslog, set debug level
@ -328,13 +331,16 @@ main(int argc, char **argv)
goto done;
}
/* Create top-level yang spec and store as option */
if ((yspecfg = yspec_new()) == NULL)
goto done;
/* Find and read configfile */
if (clicon_options_main(h) < 0){
if (clicon_options_main(h, yspecfg) < 0){
if (help)
usage(h, argv[0]);
return -1;
}
clicon_config_yang_set(h, yspecfg);
/* Now rest of options */
opterr = 0;
optind = 1;
@ -354,7 +360,10 @@ main(int argc, char **argv)
case '1' : /* Quit after reading database once - dont wait for events */
once = 1;
break;
case 'u': /* config unix domain path/ ip host */
case 'a': /* internal backend socket address family */
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
break;
case 'u': /* internal backend socket unix domain path or ip host */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_SOCK", optarg);
@ -381,8 +390,8 @@ main(int argc, char **argv)
case 'L' : /* Debug print dynamic CLI syntax */
logclisyntax++;
break;
case 'y' :{ /* Overwrite yang module or absolute filename */
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
case 'y' :{ /* Load yang spec file (override yang main module) */
yang_filename = optarg;
break;
}
case 'c' :{ /* Overwrite clispec with absolute filename */
@ -416,8 +425,24 @@ main(int argc, char **argv)
*/
cv_exclude_keys(clicon_cli_varonly(h));
/* Parse db specification as cli*/
if ((yspec = yang_spec_main(h)) == NULL)
/* Create top-level and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
/* Load main application yang specification either module or specific file
* If -y <file> is given, it overrides main module */
if (yang_filename){
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, &ymod) < 0)
goto done;
}
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
clicon_yang_dir(h),
clicon_yang_module_revision(h),
yspec, &ymod) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
if (printspec)
yang_print(stdout, (yang_node*)yspec);
@ -426,18 +451,22 @@ main(int argc, char **argv)
* the only one.
*/
if (clicon_cli_genmodel(h)){
parse_tree pt = {0,}; /* cli parse tree */
parse_tree pt = {0,}; /* cli parse tree */
char *name; /* main module name */
/* Create cli command tree from dbspec */
if (yang2cli(h, yspec, &pt, clicon_cli_genmodel_type(h)) < 0)
goto done;
len = strlen("datamodel:") + strlen(clicon_dbspec_name(h)) + 1;
/* name of main module */
name = ymod->ys_argument;
len = strlen("datamodel:") + strlen(name) + 1;
if ((treename = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
snprintf(treename, len, "datamodel:%s", clicon_dbspec_name(h));
snprintf(treename, len, "datamodel:%s", name);
cligen_tree_add(cli_cligen(h), treename, pt);
if (printgen)

View file

@ -715,7 +715,17 @@ prompt_fmt(char *prompt,
cprintf(cb, "%%");
cprintf(cb, "%c", *s);
}
}
}
else if (*s == '\\' && *++s) {
switch(*s) {
case 'n':
cprintf(cb, "\n");
break;
default:
cprintf(cb, "\\");
cprintf(cb, "%c", *s);
}
}
else
cprintf(cb, "%c", *s);
s++;

View file

@ -686,13 +686,4 @@ cli_show_auto(clicon_handle h,
return retval;
}
#ifdef COMPAT_CLIV
int show_yangv(clicon_handle h, cvec *vars, cvec *argv)
{
return show_yang(h, vars, argv);
}
int show_confv_xpath(clicon_handle h, cvec *vars, cvec *argv)
{
return show_conf_xpath(h, vars, argv);
}
#endif /* COMPAT_CLIV */

View file

@ -144,25 +144,4 @@ int cli_show_config(clicon_handle h, cvec *cvv, cvec *argv);
int cli_show_auto(clicon_handle h, cvec *cvv, cvec *argv);
#ifdef COMPAT_CLIV
int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv);
int cli_delv(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv);
int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv);
int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv);
int cli_quitv(clicon_handle h, cvec *vars, cvec *argv);
int cli_commitv(clicon_handle h, cvec *vars, cvec *argv);
int cli_validatev(clicon_handle h, cvec *vars, cvec *argv);
int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv);
int load_config_filev(clicon_handle h, cvec *vars, cvec *argv);
int save_config_filev(clicon_handle h, cvec *vars, cvec *argv);
int delete_allv(clicon_handle h, cvec *vars, cvec *argv);
int discard_changesv(clicon_handle h, cvec *vars, cvec *argv);
int cli_notifyv(clicon_handle h, cvec *cvv, cvec *argv);
int show_yangv(clicon_handle h, cvec *vars, cvec *argv);
int show_confv_xpath(clicon_handle h, cvec *cvv, cvec *argv);
#endif /* COMPAT_CLIV */
#endif /* _CLIXON_CLI_API_H_ */

View file

@ -105,28 +105,82 @@ netconf_hello_dispatch(cxobj *xn)
return retval;
}
/*
* netconf_create_hello
* create capability string (once)
/*! Create Netconf hello. Single cap and defer individual to querying modules
* @param[in] h Clicon handle
* @param[in] cb Msg buffer
* @param[in] session_id Id of client session
* Lots of dependencies here. regarding the hello protocol.
* RFC6241 NETCONF Protocol says: (8.1)
* MUST send a <hello> element containing a list of that peer's capabilities
* MUST send at least the base NETCONF capability, urn:ietf:params:netconf:base:1.1
* MAY include capabilities for previous NETCONF versions
* MUST include a <session-id>
* the example shows urn:ietf:params:netconf:capability:startup:1.0
* RFC5277 NETCONF Event Notifications
* urn:ietf:params:netconf:capability:notification:1.0 is advertised during the capability exchange
*
* RFC6022 YANG Module for NETCONF Monitoring
* MUST advertise the capability URI "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring"
* RFC7895 Yang module library defines how to announce module features (not hell capabilities)
* RFC7950 YANG 1.1 says (5.6.4);
* MUST announce the modules it implements by implementing the YANG module
* "ietf-yang-library" (RFC7895) and listing all implemented modules in the
* "/modules-state/module" list.
* MUST advertise urn:ietf:params:netconf:capability:yang-library:1.0?
* revision=<date>&module-set-id=<id> in the <hello> message.
*
* Question: should the NETCONF in RFC6241 sections 8.2-8.9 be announced both
* as features and as capabilities in the <hello> message according to RFC6241?
* urn:ietf:params:netconf:capability:candidate:1:0 (8.3)
* urn:ietf:params:netconf:capability:validate:1.1 (8.6)
* urn:ietf:params:netconf:capability:startup:1.0 (8.7)
* urn:ietf:params:netconf:capability:xpath:1.0 (8.9)
* urn:ietf:params:netconf:capability:notification:1.0 (RFC5277)
*
* @note the hello message is created bythe netconf application, not the
* backend, and backend may implement more modules - please consider if using
* library routines for detecting capabilities here. In contrast, yang module
* list (RFC7895) is processed by the backend.
* @note encode bodies, see xml_chardata_encode()
* @see yang_modules_state_get
* @see netconf_module_load
*/
int
netconf_create_hello(cbuf *xf, /* msg buffer */
int session_id)
netconf_create_hello(clicon_handle h,
cbuf *cb,
int session_id)
{
int retval = 0;
int retval = -1;
char *module_set_id;
char *ietf_yang_library_revision;
char *encstr = NULL;
add_preamble(xf);
cprintf(xf, "<hello>");
cprintf(xf, "<capabilities>");
cprintf(xf, "<capability>urn:ietf:params:xml:ns:netconf:base:1.0</capability>\n");
cprintf(xf, "<capability>urn:ietf:params:xml:ns:netconf:capability:candidate:1:0</capability>\n");
cprintf(xf, "<capability>urn:ietf:params:xml:ns:netconf:capability:validate:1.0</capability>\n");
cprintf(xf, "<capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>\n");
cprintf(xf, "<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>\n");
cprintf(xf, "<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>\n");
cprintf(xf, "</capabilities>");
cprintf(xf, "<session-id>%lu</session-id>", (long unsigned int)42+session_id);
cprintf(xf, "</hello>");
add_postamble(xf);
module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID");
if ((ietf_yang_library_revision = yang_modules_revision(h)) == NULL)
goto done;
add_preamble(cb);
cprintf(cb, "<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
cprintf(cb, "<capabilities>");
cprintf(cb, "<capability>urn:ietf:params:netconf:base:1.0</capability>");
if (xml_chardata_encode(&encstr, "urn:ietf:params:netconf:capability:yang-library:1.0?revision=%s&module-set-id=%s",
ietf_yang_library_revision,
module_set_id) < 0)
goto done;
cprintf(cb, "<capability>%s</capability>", encstr);
cprintf(cb, "<capability>urn:ietf:params:netconf:capability:candidate:1:0</capability>");
cprintf(cb, "<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>");
cprintf(cb, "<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>");
cprintf(cb, "<capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>");
cprintf(cb, "<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>");
cprintf(cb, "</capabilities>");
cprintf(cb, "<session-id>%lu</session-id>", (long unsigned int)session_id);
cprintf(cb, "</hello>");
add_postamble(cb);
retval = 0;
done:
if (encstr)
free(encstr);
return retval;
}

View file

@ -40,7 +40,7 @@
/*
* Prototypes
*/
int netconf_create_hello(cbuf *xf, int session_id);
int netconf_create_hello(clicon_handle h, cbuf *cb, int session_id);
int netconf_hello_dispatch(cxobj *xn);

View file

@ -182,6 +182,9 @@ netconf_get_target(cxobj *xn,
* @param[in] s
* @param[in] cb Cligen buffer that contains the XML message
* @param[in] msg Only for debug
* @note Assumes "cb" contains valid XML, ie encoding is correct. This is done
* if it is output by a xml render routine (xml_print et al), but NOT
* otherwise.
*/
int
netconf_output(int s,

View file

@ -71,7 +71,9 @@
#include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */
#define NETCONF_OPTS "hDqf:d:Sy:U:"
#define NETCONF_OPTS "hD:f:l:qa:u:d:y:U:t:"
#define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
/*! Process incoming packet
* @param[in] h Clicon handle
@ -89,16 +91,18 @@ process_incoming_packet(clicon_handle h,
cxobj *xret = NULL; /* Return (out) */
cxobj *xrpc;
cxobj *xc;
yang_spec *yspec;
clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
yspec = clicon_dbspec_yang(h);
if ((str0 = strdup(cbuf_get(cb))) == NULL){
clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno));
return -1;
}
str = str0;
/* Parse incoming XML message */
if (xml_parse_string(str, NULL, &xreq) < 0){
if (xml_parse_string(str, yspec, &xreq) < 0){
if ((cbret = cbuf_new()) == NULL){
if (netconf_operation_failed(cbret, "rpc", "internal error")< 0)
goto done;
@ -239,48 +243,60 @@ netconf_input_cb(int s,
return retval;
}
/*
* send_hello
* args: s file descriptor to write on (eg 1 - stdout)
/*! Send netconf hello message
* @param[in] h Clicon handle
* @param[in] s File descriptor to write on (eg 1 - stdout)
*/
static int
send_hello(int s)
send_hello(clicon_handle h,
int s)
{
cbuf *xf;
int retval = -1;
int retval = -1;
cbuf *cb;
if ((xf = cbuf_new()) == NULL){
if ((cb = cbuf_new()) == NULL){
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
goto done;
}
if (netconf_create_hello(xf, getpid()) < 0)
if (netconf_create_hello(h, cb, getpid()) < 0)
goto done;
if (netconf_output(s, xf, "hello") < 0)
if (netconf_output(s, cb, "hello") < 0)
goto done;
retval = 0;
done:
if (xf)
cbuf_free(xf);
if (cb)
cbuf_free(cb);
return retval;
}
static int
netconf_terminate(clicon_handle h)
{
yang_spec *yspec;
yang_spec *yspec;
cxobj *x;
clixon_plugin_exit(h);
rpc_callback_delete_all();
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
if ((yspec = clicon_netconf_yang(h)) != NULL)
if ((yspec = clicon_config_yang(h)) != NULL)
yspec_free(yspec);
if ((x = clicon_conf_xml(h)) != NULL)
xml_free(x);
event_exit();
clicon_handle_exit(h);
clicon_log_exit();
return 0;
}
static int
timeout_fn(int s,
void *arg)
{
clicon_err(OE_EVENTS, ETIME, "User request timeout");
return -1;
}
/*! Usage help routine
* @param[in] h Clicon handle
@ -293,13 +309,17 @@ usage(clicon_handle h,
fprintf(stderr, "usage:%s\n"
"where options are\n"
"\t-h\t\tHelp\n"
"\t-D\t\tDebug\n"
"\t-D <level>\tDebug level\n"
"\t-q\t\tQuiet: dont send hello prompt\n"
"\t-f <file>\tConfiguration file (mandatory)\n"
"\t-l (e|o|s|f<file>) \tLog on std(e)rr, std(o)ut, (s)yslog, (f)ile (syslog is default)\n"
"\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n"
"\t-d <dir>\tSpecify netconf plugin directory dir (default: %s)\n"
"\t-S\t\tLog on syslog\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n",
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-U <user>\tOver-ride unix user with a pseudo user for NACM.\n"
"\t-t <sec>\tTimeout in seconds. Quit after this time.\n",
argv0,
clicon_netconf_dir(h)
);
@ -315,18 +335,19 @@ main(int argc,
char *argv0 = argv[0];
int quiet = 0;
clicon_handle h;
int use_syslog;
char *dir;
int logdst = CLICON_LOG_STDERR;
struct passwd *pw;
struct timeval tv = {0,}; /* timeout */
yang_spec *yspec = NULL;
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
char *yang_filename = NULL;
/* Defaults */
use_syslog = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
/* Create handle */
if ((h = clicon_handle_init()) == NULL)
return -1;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
/* Set username to clicon handle. Use in all communication to backend */
if ((pw = getpwuid(getuid())) == NULL){
@ -335,35 +356,43 @@ main(int argc,
}
if (clicon_username_set(h, pw->pw_name) < 0)
goto done;
while ((c = getopt(argc, argv, NETCONF_OPTS)) != -1)
switch (c) {
case 'h' : /* help */
usage(h, argv[0]);
break;
case 'D' : /* debug */
debug = 1;
if (sscanf(optarg, "%d", &debug) != 1)
usage(h, argv[0]);
break;
case 'f': /* override config file */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
case 'S': /* Log on syslog */
use_syslog = 1;
case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]);
if (logdst == CLICON_LOG_FILE &&
strlen(optarg)>1 &&
clicon_log_file(optarg+1) < 0)
goto done;
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_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_debug_init(debug, NULL);
/* Create configure yang-spec */
if ((yspecfg = yspec_new()) == NULL)
goto done;
/* Find and read configfile */
if (clicon_options_main(h) < 0)
if (clicon_options_main(h, yspecfg) < 0)
return -1;
clicon_config_yang_set(h, yspecfg);
/* Now rest of options */
optind = 1;
opterr = 0;
@ -371,9 +400,17 @@ main(int argc,
switch (c) {
case 'h' : /* help */
case 'D' : /* debug */
case 'f': /* config file */
case 'S': /* Log on syslog */
case 'f': /* config file */
case 'l': /* log */
break; /* see above */
case 'a': /* internal backend socket address family */
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
break;
case 'u': /* internal backend socket unix domain path or ip host */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_SOCK", optarg);
break;
case 'q': /* quiet: dont write hello */
quiet++;
break;
@ -382,8 +419,8 @@ main(int argc,
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg);
break;
case 'y' :{ /* Overwrite yang module or absolute filename */
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
case 'y' :{ /* Load yang spec file (override yang main module) */
yang_filename = optarg;
break;
}
case 'U': /* Clixon 'pseudo' user */
@ -392,6 +429,10 @@ main(int argc,
if (clicon_username_set(h, optarg) < 0)
goto done;
break;
case 't': /* timeout in seconds */
tv.tv_sec = atoi(optarg);
break;
default:
usage(h, argv[0]);
break;
@ -399,16 +440,28 @@ main(int argc,
argc -= optind;
argv += optind;
/* Parse yang database spec file */
if (yang_spec_main(h) == NULL)
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
/* Parse netconf yang spec file */
if (yang_spec_netconf(h) == NULL)
clicon_dbspec_yang_set(h, yspec);
/* Load main application yang specification either module or specific file
* If -y <file> is given, it overrides main module */
if (yang_filename){
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
goto done;
}
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
clicon_yang_dir(h),
clicon_yang_module_revision(h),
yspec, NULL) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
/* Add netconf yang spec, used by netconf client and as internal protocol */
if (netconf_module_load(h) < 0)
goto done;
/* Initialize plugins group */
if ((dir = clicon_netconf_dir(h)) != NULL)
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
@ -421,11 +474,18 @@ main(int argc,
*(argv-1) = tmp;
if (!quiet)
send_hello(1);
send_hello(h, 1);
if (event_reg_fd(0, netconf_input_cb, h, "netconf socket") < 0)
goto done;
if (debug)
clicon_option_dump(h, debug);
if (tv.tv_sec || tv.tv_usec){
struct timeval t;
gettimeofday(&t, NULL);
timeradd(&t, &tv, &t);
if (event_reg_timeout(t, timeout_fn, NULL, "timeout") < 0)
goto done;
}
if (event_loop() < 0)
goto done;
done:

View file

@ -728,25 +728,27 @@ netconf_discard_changes(clicon_handle h,
<severity>major</severity>
</event>
</notification>
* @see rfc5277:
* An event notification is sent to the client who initiated a
* <create-subscription> command asynchronously when an event of
* interest...
* Parameters: eventTime type dateTime and compliant to [RFC3339]
* Also contains notification-specific tagged content, if any. With
* the exception of <eventTime>, the content of the notification is
* beyond the scope of this document.
*/
static int
netconf_notification_cb(int s,
void *arg)
{
cxobj *xfilter = (cxobj *)arg;
char *selector;
struct clicon_msg *reply = NULL;
int eof;
char *event = NULL;
int retval = -1;
cbuf *cb;
cxobj *xe = NULL; /* event xml */
cxobj *xn = NULL; /* event xml */
cxobj *xt = NULL; /* top xml */
if (0){
fprintf(stderr, "%s\n", __FUNCTION__); /* debug */
xml_print(stderr, xfilter); /* debug */
}
clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */
if (clicon_msg_rcv(s, &reply, &eof) < 0)
goto done;
@ -756,31 +758,20 @@ netconf_notification_cb(int s,
close(s);
errno = ESHUTDOWN;
event_unreg_fd(s, netconf_notification_cb);
if (xfilter)
xml_free(xfilter);
goto done;
}
if (clicon_msg_decode(reply, &xt) < 0)
goto done;
if ((xe = xpath_first(xt, "//event")) != NULL)
event = xml_body(xe);
/* parse event */
if (0){ /* XXX CLICON events are not xml */
/* find and apply filter */
if ((selector = xml_find_value(xfilter, "select")) == NULL)
goto done;
if (xpath_first(xe, "%s", selector) == NULL) {
fprintf(stderr, "%s no match\n", __FUNCTION__); /* debug */
}
}
if ((xn = xpath_first(xt, "notification")) == NULL)
goto ok;
/* create netconf message */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
add_preamble(cb); /* Make it well-formed netconf xml */
cprintf(cb, "<notification><event>%s</event></notification>", event);
if (clicon_xml2cbuf(cb, xn, 0, 0) < 0)
goto done;
add_postamble(cb);
/* Send it to listening client on stdout */
if (netconf_output(1, cb, "notification") < 0){
@ -789,6 +780,7 @@ netconf_notification_cb(int s,
}
fflush(stdout);
cbuf_free(cb);
ok:
retval = 0;
done:
if (xt != NULL)
@ -802,7 +794,7 @@ netconf_notification_cb(int s,
<create-subscription>
<stream>RESULT</stream> # If not present, events in the default NETCONF stream will be sent.
<filter>XPATH-EXPR<(filter>
<filter type="xpath" select="XPATHEXPR"/>
<startTime/> # only for replay (NYI)
<stopTime/> # only for replay (NYI)
</create-subscription>
@ -810,7 +802,7 @@ netconf_notification_cb(int s,
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at <rpc>...</rpc> level.
* @param[out] xret Return XML, error or OK
* @see netconf_notification_cb for asynchronous stream notifications
*/
static int
netconf_create_subscription(clicon_handle h,
@ -838,9 +830,11 @@ netconf_create_subscription(clicon_handle h,
}
if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, &s) < 0)
goto done;
if (xpath_first(*xret, "rpc-reply/rpc-error") != NULL)
goto ok;
if (event_reg_fd(s,
netconf_notification_cb,
xfilter?xml_dup(xfilter):NULL,
NULL,
"notification socket") < 0)
goto done;
ok:
@ -971,15 +965,9 @@ netconf_rpc_dispatch(clicon_handle h,
{
int retval = -1;
cxobj *xe;
yang_spec *yspec = NULL;
char *username;
cxobj *xa;
/* Check incoming RPC against system / netconf RPC:s */
if ((yspec = clicon_netconf_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No netconf yang spec");
goto done;
}
/* Tag username on all incoming requests in case they are forwarded as internal messages
* This may be unecesary since not all are forwarded.
* It may even be wrong if something else is done with the incoming message?

View file

@ -45,12 +45,14 @@ bindir = @bindir@
libdir = @libdir@
mandir = @mandir@
libexecdir = @libexecdir@
wwwdir = /www-data
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
includedir = @includedir@
HOST_VENDOR = @host_vendor@
wwwdir = @wwwdir@
wwwuser = @wwwuser@
SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
CLIXON_MINOR = @CLIXON_VERSION_MINOR@
@ -73,6 +75,7 @@ APPL = clixon_restconf
# Not accessible from plugin
APPSRC = restconf_main.c
APPSRC += restconf_methods.c
APPSRC += restconf_stream.c
APPOBJ = $(APPSRC:.c=.o)
# Accessible from plugin
@ -97,8 +100,13 @@ distclean: clean
# Put other executables in libexec/
# Also create a libexec/ directory for writeable/temporary files.
# Put config file in etc/
# Also a rule for letting www-dir be owned by www-data, which only orks for sudo
install: install-lib $(APPL)
ifeq ($(shell whoami),root)
install -d -m 0755 -o $(wwwuser) -g $(wwwuser) $(DESTDIR)$(wwwdir)
else
install -d -m 0755 $(DESTDIR)$(wwwdir)
endif
install -m 0755 $(INSTALLFLAGS) $(APPL) $(DESTDIR)$(wwwdir)
install-lib: $(MYLIB)

View file

@ -1,23 +1,37 @@
# Clixon Restconf
### Installation using Nginx
* [Installation](#Installation)
* [Streams](Streams)
* [Nchan Streams](Nchan)
* [Debugging](Debugging)
### 1. Installation
The examples are based on Nginx. Other reverse proxies should work but are not verified.
Ensure www-data is member of the CLICON_SOCK_GROUP (default clicon). If not, add it:
```
sudo usermod -a -G clicon www-data
```
This implementation uses FastCGI, see http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html.
Download and start nginx. For example on ubuntu:
```
sudo apt install ngnix
```
Define nginx config file: /etc/nginx/sites-available/default
```
server {
...
location /restconf {
root /usr/share/nginx/html/restconf;
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
}
```
Start nginx daemon
```
sudo /etc/init.d nginx start
@ -62,11 +76,115 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
```
### Debugging
### 2. Streams
Clixon have two experimental restconf event stream implementations following
RFC8040 Section 6 using SSE. One native and one using Nginx
nchan. The Nchan alternaitve is described in the
next section.
The (example)[../../example/README.md] creates an EXAMPLE stream.
Set the Clixon configuration options:
```
<CLICON_STREAM_PATH>streams</CLICON_STREAM_PATH>
<CLICON_STREAM_URL>https://example.com</CLICON_STREAM_URL>
<CLICON_STREAM_RETENTION>3600</CLICON_STREAM_RETENTION>
```
In this example, the stream EXAMPLE would be accessed with `https://example.com/streams/EXAMPLE`.
The retention is configured as 1 hour, i.e., the stream replay function will only save timeseries one other.
Clixon defines an internal in-memory (not persistent) replay function
controlled by the configure option above.
You may access a restconf streams using curl.
Add the following to extend the nginx configuration file with the following statements (for example):
```
location /streams {
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
```
AN example of a stream access is as follows:
```
vandal> curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:11.373124</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T14:47:16.375265</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
```
You can also specify start and stop time. Start-time enables replay of existing samples, while stop-time is used both for replay, but also for stopping a stream at some future time.
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE?start-time=2014-10-25T10:02:00&stop-time=2014-10-25T12:31:00
```
See (stream tests)[../test/test_streams.sh] for more examples.
### 3. Nchan
As an alternative streams implementation, Nginx/Nchan can be used.
Nginx uses pub/sub channels and can be configured in a variety of
ways. The following uses a simple variant with one generic subscription
channel (streams) and one publication channel (pub).
The advantage with Nchan is the large eco-system around Nginx and Nchan.
Native mode and Nchan mode can co-exist, but the publish URL of Nchan should be different from the streams URL of the native streams.
Nchan mode does not use Clixon retention, since it uses its own replay mechanism.
Download and install nchan, see (https://nchan.io/#install).
Add the following to extend the Nginx configuration file with the following statements (example):
```
location ~ /sub/(\w+)$ {
nchan_subscriber;
nchan_channel_id $1; #first capture of the location match
}
location ~ /pub/(\w+)$ {
nchan_publisher;
nchan_channel_id $1; #first capture of the location match
}
```
Configure clixon with `--enable-publish` which enables curl code for
publishing streams to nchan.
You also need to configure CLICON_STREAM_PUB to enable pushing notifications to Nginx/Nchan. Example:
```
<CLICON_STREAM_PUB>http://localhost/pub</CLICON_STREAM_PUB>
```
Clicon will then publish events from stream EXAMPLE to `http://localhost/pub/EXAMPLE
Access the event stream EXAMPLE using curl:
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
: hi
id: 1541344320:0
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T15:12:00.435769</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
id: 1541344325:0
data: <notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-11-04T15:12:05.446425</eventTime><event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>
```
Note that the SSE stream output is different than for native streams, and that `Last-Event-Id` is used for replay:
```
curl -H "Accept: text/event-stream" -H "Last-Event-ID: 1539961709:0" -s -X GET http://localhost/streams/EXAMPLE
```
See (https://nchan.io/#eventsource) on more info on how to access an SSE sub endpoint.
### 4. Debugging
Start the restconf fastcgi program with debug flag:
```
sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml" -s /bin/sh www-data
sudo su -c "/www-data/clixon_restconf -D 1 -f /usr/local/etc/example.xml" -s /bin/sh www-data
```
Look at syslog:
```
@ -77,3 +195,13 @@ Send command:
```
curl -G http://127.0.0.1/restconf/data/*
```
You can also run restconf in a debugger.
```
sudo gdb /www-data/clixon_restconf
(gdb) run -D 1 -f /usr/local/etc/example.xml
```
but you need to ensure /www-data/fastcgi_restconf.sock has the following access:
```
rwxr-xr-x 1 www-data www-data 0 sep 22 11:46 /www-data/fastcgi_restconf.sock
```

View file

@ -54,7 +54,7 @@
/* clicon */
#include <clixon/clixon.h>
#include <fcgi_stdio.h> /* Need to be after clixon_xml-h due to attribute format */
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
@ -221,6 +221,26 @@ notfound(FCGX_Request *r)
return 0;
}
/*! HTTP error 406 Not acceptable
* @param[in] r Fastcgi request handle
*/
int
notacceptable(FCGX_Request *r)
{
char *path;
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
FCGX_FPrintF(r->out, "Status: 406\r\n"); /* 406 not acceptible */
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
FCGX_FPrintF(r->out, "<h1>Not Acceptable</h1>\n");
FCGX_FPrintF(r->out, "Not Acceptable\n");
FCGX_FPrintF(r->out, "The target resource does not have a current representation that would be acceptable to the user agent.\n",
path);
return 0;
}
/*! HTTP error 409
* @param[in] r Fastcgi request handle
*/
@ -465,3 +485,23 @@ api_return_err(clicon_handle h,
cbuf_free(cb);
return retval;
}
int
restconf_terminate(clicon_handle h)
{
yang_spec *yspec;
cxobj *x;
clixon_plugin_exit(h);
rpc_callback_delete_all();
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
if ((yspec = clicon_config_yang(h)) != NULL)
yspec_free(yspec);
if ((x = clicon_conf_xml(h)) != NULL)
xml_free(x);
clicon_handle_exit(h);
clicon_log_exit();
return 0;
}

View file

@ -36,6 +36,11 @@
#ifndef _RESTCONF_LIB_H_
#define _RESTCONF_LIB_H_
/*
* Constants
*/
#define RESTCONF_API "restconf"
/*
* Prototypes (also in clixon_restconf.h)
*/
@ -46,6 +51,7 @@ int badrequest(FCGX_Request *r);
int unauthorized(FCGX_Request *r);
int forbidden(FCGX_Request *r);
int notfound(FCGX_Request *r);
int notacceptable(FCGX_Request *r);
int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r);
@ -56,5 +62,6 @@ cbuf *readdata(FCGX_Request *r);
int get_user_cookie(char *cookiestr, char *attribute, char **val);
int api_return_err(clicon_handle h, FCGX_Request *r, cxobj *xerr,
int pretty, int use_xml);
int restconf_terminate(clicon_handle h);
#endif /* _RESTCONF_LIB_H_ */

View file

@ -34,18 +34,24 @@
*/
/*
* This program should be run as user www-data
*
* See draft-ietf-netconf-restconf-13.txt [draft]
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
* sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data
* sudo su -c "/www-data/clixon_restconf -D 1 -f /usr/local/etc/example.xml " -s /bin/sh www-data
* This is the interface:
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
* api/test
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@ -67,14 +73,15 @@
/* clicon */
#include <clixon/clixon.h>
#include <fcgi_stdio.h> /* Need to be after clixon_xml-h due to attribute format */
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
/* restconf */
#include "restconf_lib.h"
#include "restconf_methods.h"
#include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hDf:p:y:"
#define RESTCONF_OPTS "hD:f:l:p:y:a:u:"
/* RESTCONF enables deployments to specify where the RESTCONF API is
located. The client discovers this by getting the "/.well-known/host-meta"
@ -82,9 +89,6 @@
*/
#define RESTCONF_WELL_KNOWN "/.well-known/host-meta"
#define RESTCONF_API "restconf"
#define RESTCONF_API_ROOT "/restconf"
/*! Generic REST method, GET, PUT, DELETE, etc
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
@ -262,6 +266,7 @@ api_yang_library_version(clicon_handle h,
cxobj *xt = NULL;
cbuf *cb = NULL;
int pretty;
char *ietf_yang_library_revision = "2016-06-21"; /* XXX */
clicon_debug(1, "%s", __FUNCTION__);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
@ -271,7 +276,7 @@ api_yang_library_version(clicon_handle h,
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json");
FCGX_FPrintF(r->out, "\r\n");
if (xml_parse_string("<yang-library-version>2016-06-21</yang-library-version>", NULL, &xt) < 0)
if (xml_parse_va(&xt, NULL, "<yang-library-version>%s</yang-library-version>", ietf_yang_library_revision) < 0)
goto done;
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
@ -438,21 +443,8 @@ api_restconf(clicon_handle h,
return retval;
}
static int
restconf_terminate(clicon_handle h)
{
yang_spec *yspec;
clixon_plugin_exit(h);
rpc_callback_delete_all();
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
clicon_handle_exit(h);
return 0;
}
/* Need global variable to for signal handler */
/* Need global variable to for signal handler XXX */
static clicon_handle _CLICON_HANDLE = NULL;
/*! Signall terminates process
@ -467,12 +459,24 @@ restconf_sig_term(int arg)
__PROGRAM__, __FUNCTION__, getpid(), arg);
else
exit(-1);
if (_CLICON_HANDLE)
if (_CLICON_HANDLE){
stream_child_freeall(_CLICON_HANDLE);
restconf_terminate(_CLICON_HANDLE);
}
clicon_exit_set(); /* checked in event_loop() */
exit(-1);
}
static void
restconf_sig_child(int arg)
{
int status;
int pid;
if ((pid = waitpid(-1, &status, 0)) != -1 && WIFEXITED(status))
stream_child_free(_CLICON_HANDLE, pid);
}
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
@ -485,10 +489,13 @@ usage(clicon_handle h,
fprintf(stderr, "usage:%s [options]\n"
"where options are\n"
"\t-h \t\tHelp\n"
"\t-D \t\tDebug. Log to syslog\n"
"\t-D <level>\tDebug level\n"
"\t-f <file>\tConfiguration file (mandatory)\n"
"\t-l <s|f<file>> \tLog on (s)yslog, (f)ile (syslog is default)\n"
"\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n",
"\t-y <file>\tLoad yang spec file (override yang main module)\n"
"\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
"\t-u <path|addr>\tInternal socket domain path or IP addr (see -a)\n",
argv0,
clicon_restconf_dir(h)
);
@ -513,12 +520,20 @@ main(int argc,
char *yangspec=NULL;
char *dir;
char *tmp;
int logdst = CLICON_LOG_SYSLOG;
yang_spec *yspec = NULL;
yang_spec *yspecfg = NULL; /* For config XXX clixon bug */
char *yang_filename = NULL;
char *stream_path;
int finish;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG);
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
/* Create handle */
if ((h = clicon_handle_init()) == NULL)
goto done;
_CLICON_HANDLE = h; /* for termination handling */
while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
switch (c) {
@ -526,29 +541,28 @@ main(int argc,
usage(h, argv[0]);
break;
case 'D' : /* debug */
debug = 1;
if (sscanf(optarg, "%d", &debug) != 1)
usage(h, argv[0]);
break;
case 'f': /* override config file */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
case 'd': /* Plugin directory */
if (!strlen(optarg))
case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
break;
case 'y' : /* yang module */
yangspec = optarg;
break;
default:
usage(h, argv[0]);
break;
}
argc -= optind;
argv += optind;
if (logdst == CLICON_LOG_FILE &&
strlen(optarg)>1 &&
clicon_log_file(optarg+1) < 0)
goto done;
break;
} /* switch getopt */
/*
* Logs, error and debug to stderr or syslog, set debug level
*/
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, logdst);
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_debug_init(debug, NULL);
clicon_log(LOG_NOTICE, "%s: %u Started", __PROGRAM__, getpid());
if (set_signal(SIGTERM, restconf_sig_term, NULL) < 0){
@ -559,10 +573,51 @@ main(int argc,
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
/* Find and read configfile */
if (clicon_options_main(h) < 0)
if (set_signal(SIGCHLD, restconf_sig_child, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
/* Create configure yang-spec */
if ((yspecfg = yspec_new()) == NULL)
goto done;
/* Find and read configfile */
if (clicon_options_main(h, yspecfg) < 0)
goto done;
clicon_config_yang_set(h, yspecfg);
stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
/* Now rest of options, some overwrite option file */
optind = 1;
opterr = 0;
while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
switch (c) {
case 'h' : /* help */
case 'D' : /* debug */
case 'f': /* config file */
case 'l': /* log */
break; /* see above */
case 'd': /* Plugin directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
break;
case 'y' : /* Load yang spec file (override yang main module) */
yang_filename = optarg;
break;
case 'a': /* internal backend socket address family */
clicon_option_str_set(h, "CLICON_SOCK_FAMILY", optarg);
break;
case 'u': /* internal backend socket unix domain path or ip host */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_SOCK", optarg);
break;
default:
usage(h, argv[0]);
break;
}
argc -= optind;
argv += optind;
/* Overwrite yang module with -y option */
if (yangspec)
@ -573,10 +628,32 @@ main(int argc,
if (clixon_plugins_load(h, CLIXON_PLUGIN_INIT, dir, NULL) < 0)
return -1;
/* Parse yang database spec file */
if (yang_spec_main(h) == NULL)
/* Create top-level yang spec and store as option */
if ((yspec = yspec_new()) == NULL)
goto done;
clicon_dbspec_yang_set(h, yspec);
/* Load main application yang specification either module or specific file
* If -y <file> is given, it overrides main module */
if (yang_filename){
if (yang_spec_parse_file(h, yang_filename, clicon_yang_dir(h), yspec, NULL) < 0)
goto done;
}
else if (yang_spec_parse_module(h, clicon_yang_module_main(h),
clicon_yang_dir(h),
clicon_yang_module_revision(h),
yspec, NULL) < 0)
goto done;
/* Load yang module library, RFC7895 */
if (yang_modules_init(h) < 0)
goto done;
/* Add system modules */
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC8040") &&
yang_spec_parse_module(h, "ietf-restconf-monitoring", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277") &&
yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
/* Call start function in all plugins before we go interactive
Pass all args after the standard options to plugin_start
*/
@ -589,29 +666,34 @@ main(int argc,
clicon_err(OE_CFG, errno, "No CLICON_RESTCONF_PATH in clixon configure file");
goto done;
}
if (FCGX_Init() != 0){
if (FCGX_Init() != 0){ /* How to cleanup memory after this? */
clicon_err(OE_CFG, errno, "FCGX_Init");
goto done;
}
clicon_debug(1, "restconf_main: Opening FCGX socket: %s", sockpath);
if ((sock = FCGX_OpenSocket(sockpath, 10)) < 0){
clicon_err(OE_CFG, errno, "FCGX_OpenSocket");
goto done;
}
if (FCGX_InitRequest(r, sock, 0) != 0){
clicon_err(OE_CFG, errno, "FCGX_InitRequest");
goto done;
}
while (1) {
finish = 1; /* If zero, dont finish request, initiate new */
if (FCGX_Accept_r(r) < 0) {
clicon_err(OE_CFG, errno, "FCGX_Accept_r");
goto done;
}
clicon_debug(1, "------------");
if ((path = FCGX_GetParam("REQUEST_URI", r->envp)) != NULL){
clicon_debug(1, "path:%s", path);
if (strncmp(path, RESTCONF_API_ROOT, strlen(RESTCONF_API_ROOT)) == 0)
clicon_debug(1, "path: %s", path);
if (strncmp(path, "/" RESTCONF_API, strlen("/" RESTCONF_API)) == 0)
api_restconf(h, r); /* This is the function */
else if (strncmp(path+1, stream_path, strlen(stream_path)) == 0) {
api_stream(h, r, stream_path, &finish);
}
else if (strncmp(path, RESTCONF_WELL_KNOWN, strlen(RESTCONF_WELL_KNOWN)) == 0) {
api_well_known(h, r); /* */
}
@ -622,10 +704,19 @@ main(int argc,
}
else
clicon_debug(1, "NULL URI");
FCGX_Finish_r(r);
if (finish)
FCGX_Finish_r(r);
else{ /* A handler is forked so we initiate a new request after instead
of finnishing the old */
if (FCGX_InitRequest(r, sock, 0) != 0){
clicon_err(OE_CFG, errno, "FCGX_InitRequest");
goto done;
}
}
}
retval = 0;
done:
stream_child_freeall(h);
restconf_terminate(h);
return retval;
}

View file

@ -39,7 +39,7 @@
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
* sudo su -c "/www-data/clixon_restconf -Df /usr/local/etc/example.xml " -s /bin/sh www-data
* sudo su -c "/www-data/clixon_restconf -D 1 f /usr/local/etc/example.xml " -s /bin/sh www-data
* This is the interface:
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
@ -118,7 +118,7 @@ Mapping netconf error-tag -> status code
/* clicon */
#include <clixon/clixon.h>
#include <fcgi_stdio.h> /* Need to be after clixon_xml-h due to attribute format */
#include <fcgiapp.h> /* Need to be after clixon_xml-h due to attribute format */
#include "restconf_lib.h"
#include "restconf_methods.h"

View file

@ -0,0 +1,507 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 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 *****
Restconf event stream implementation.
See RFC 8040 RESTCONF Protocol
Sections 3.8, 6, 9.3
RFC8040:
A RESTCONF server MAY send the "retry" field, and if it does, RESTCONF
clients SHOULD use it. A RESTCONF server SHOULD NOT send the "event"
or "id" fields, as there are no meaningful values. RESTCONF
servers that do not send the "id" field also do not need to support
the HTTP header field "Last-Event-ID"
The RESTCONF client can then use this URL value to start monitoring
the event stream:
GET /streams/NETCONF HTTP/1.1
Host: example.com
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
The server MAY support the "start-time", "stop-time", and "filter"
query parameters, defined in Section 4.8. Refer to Appendix B.3.6
for filter parameter examples.
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <time.h>
#include <limits.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <libgen.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <fcgiapp.h> /* Need to be after clixon_xml.h due to attribute format */
#include "restconf_lib.h"
#include "restconf_stream.h"
/*
* Constants
*/
/* Enable for forking stream subscription loop.
* Disable to get single threading but blocking on streams
*/
#define STREAM_FORK 1
/* Keep track of children - whjen they exit - their FCGX handle needs to be
* freed with FCGX_Free(&rbk, 0);
*/
struct stream_child{
qelem_t sc_q; /* queue header */
int sc_pid; /* Child process id */
FCGX_Request sc_r; /* FCGI stream data */
};
/* Linked list of children
* @note could hang STREAM_CHILD list on clicon handle instead.
*/
static struct stream_child *STREAM_CHILD = NULL;
/*! Find restconf child using PID and cleanup FCGI Request data
* @param[in] h Clicon handle
* @param[in] pid Process id of child
* @note could hang STREAM_CHILD list on clicon handle instead.
*/
int
stream_child_free(clicon_handle h,
int pid)
{
struct stream_child *sc;
if ((sc = STREAM_CHILD) != NULL){
do {
if (pid == sc->sc_pid){
DELQ(sc, STREAM_CHILD, struct stream_child *);
FCGX_Free(&sc->sc_r, 0);
free(sc);
goto done;
}
sc = NEXTQ(struct stream_child *, sc);
} while (sc && sc != STREAM_CHILD);
}
done:
return 0;
}
int
stream_child_freeall(clicon_handle h)
{
struct stream_child *sc;
while ((sc = STREAM_CHILD) != NULL){
DELQ(sc, STREAM_CHILD, struct stream_child *);
FCGX_Free(&sc->sc_r, 1);
free(sc);
}
return 0;
}
/*! Callback when stream notifications arrive from backend
*/
static int
restconf_stream_cb(int s,
void *arg)
{
int retval = -1;
FCGX_Request *r = (FCGX_Request *)arg;
int eof;
struct clicon_msg *reply = NULL;
cxobj *xtop = NULL; /* top xml */
cxobj *xn; /* notification xml */
cbuf *cb = NULL;
int pretty = 0; /* XXX should be via arg */
clicon_debug(1, "%s", __FUNCTION__);
/* get msg (this is the reason this function is called) */
if (clicon_msg_rcv(s, &reply, &eof) < 0){
clicon_debug(1, "%s msg_rcv error", __FUNCTION__);
goto done;
}
clicon_debug(1, "%s msg: %s", __FUNCTION__, reply?reply->op_body:"null");
/* handle close from remote end: this will exit the client */
if (eof){
clicon_debug(1, "%s eof", __FUNCTION__);
clicon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
errno = ESHUTDOWN;
FCGX_FPrintF(r->out, "SHUTDOWN\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
clicon_exit_set();
goto done;
}
if (clicon_msg_decode(reply, &xtop) < 0)
goto done;
/* create event */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if ((xn = xpath_first(xtop, "notification")) == NULL)
goto ok;
#ifdef notused
xt = xpath_first(xn, "eventTime");
if ((xe = xpath_first(xn, "event")) == NULL) /* event can depend on yang? */
goto ok;
if (xt)
FCGX_FPrintF(r->out, "M#id: %s\r\n", xml_body(xt));
else{ /* XXX */
gettimeofday(&tv, NULL);
FCGX_FPrintF(r->out, "M#id: %02d:0\r\n", tv.tv_sec);
}
#endif
if (clicon_xml2cbuf(cb, xn, 0, pretty) < 0)
goto done;
FCGX_FPrintF(r->out, "data: %s\r\n", cbuf_get(cb));
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
ok:
retval = 0;
done:
clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
if (xtop != NULL)
xml_free(xtop);
if (reply)
free(reply);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send subsctription to backend
* @param[in] h Clicon handle
* @param[in] r Fastcgi request handle
* @param[in] name Stream name
* @param[out] sp Socket -1 if not set
*/
static int
restconf_stream(clicon_handle h,
FCGX_Request *r,
char *name,
cvec *qvec,
int pretty,
int use_xml,
int *sp)
{
int retval = -1;
cxobj *xret = NULL;
cxobj *xe;
cbuf *cb = NULL;
int s; /* socket */
int i;
cg_var *cv;
char *vname;
clicon_debug(1, "%s", __FUNCTION__);
*sp = -1;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<rpc><create-subscription><stream>%s</stream>", name);
/* Print all fields */
for (i=0; i<cvec_len(qvec); i++){
cv = cvec_i(qvec, i);
vname = cv_name_get(cv);
if (strcmp(vname, "start-time") == 0){
cprintf(cb, "<startTime>");
cv2cbuf(cv, cb);
cprintf(cb, "</startTime>");
}
else if (strcmp(vname, "stop-time") == 0){
cprintf(cb, "<stopTime>");
cv2cbuf(cv, cb);
cprintf(cb, "</stopTime>");
}
}
cprintf(cb, "</create-subscription></rpc>]]>]]>");
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
goto done;
if ((xe = xpath_first(xret, "rpc-reply/rpc-error")) != NULL){
if (api_return_err(h, r, xe, pretty, use_xml) < 0)
goto done;
goto ok;
}
/* Setting up stream */
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/event-stream\r\n");
FCGX_FPrintF(r->out, "Cache-Control: no-cache\r\n");
FCGX_FPrintF(r->out, "Connection: keep-alive\r\n");
FCGX_FPrintF(r->out, "X-Accel-Buffering: no\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FFlush(r->out);
*sp = s;
ok:
retval = 0;
done:
clicon_debug(1, "%s retval: %d", __FUNCTION__, retval);
if (xret)
xml_free(xret);
if (cb)
cbuf_free(cb);
return retval;
}
/* restconf */
#include "restconf_lib.h"
#include "restconf_stream.h"
static int
stream_checkuplink(int s,
void *arg)
{
FCGX_Request *r = (FCGX_Request *)arg;
clicon_debug(1, "%s", __FUNCTION__);
if (FCGX_GetError(r->out) != 0){ /* break loop */
clicon_debug(1, "%s FCGX_GetError upstream", __FUNCTION__);
clicon_exit_set();
}
return 0;
}
int
stream_timeout(int s,
void *arg)
{
struct timeval t;
struct timeval t1;
FCGX_Request *r = (FCGX_Request *)arg;
clicon_debug(1, "%s", __FUNCTION__);
if (FCGX_GetError(r->out) != 0){ /* break loop */
clicon_debug(1, "%s FCGX_GetError upstream", __FUNCTION__);
clicon_exit_set();
}
else{
gettimeofday(&t, NULL);
t1.tv_sec = 1; t1.tv_usec = 0;
timeradd(&t, &t1, &t);
event_reg_timeout(t, stream_timeout, arg, "Stream timeout");
}
return 0;
}
/*! Process a FastCGI request
* @param[in] r Fastcgi request handle
*/
int
api_stream(clicon_handle h,
FCGX_Request *r,
char *streampath,
int *finish)
{
int retval = -1;
char *path;
char *query;
char *method;
char **pvec = NULL;
int pn;
cvec *qvec = NULL;
cvec *dvec = NULL;
cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL;
char *data;
int authenticated = 0;
int pretty;
int use_xml = 1; /* default */
cbuf *cbret = NULL;
cxobj *xret = NULL;
cxobj *xerr;
int s=-1;
#ifdef STREAM_FORK
int pid;
struct stream_child *sc;
#endif
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
test(r, 1);
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
/* Sanity check of path. Should be /stream/<name> */
if (pn != 3){
notfound(r);
goto ok;
}
if (strlen(pvec[0]) != 0){
retval = notfound(r);
goto done;
}
if (strcmp(pvec[1], streampath)){
retval = notfound(r);
goto done;
}
if ((method = pvec[2]) == NULL){
retval = notfound(r);
goto done;
}
clicon_debug(1, "%s: method=%s", __FUNCTION__, method);
if (str2cvec(query, '&', '=', &qvec) < 0)
goto done;
if (str2cvec(path, '/', '=', &pcvec) < 0) /* rest url eg /album=ricky/foo */
goto done;
/* data */
if ((cb = readdata(r)) == NULL)
goto done;
data = cbuf_get(cb);
clicon_debug(1, "%s DATA=%s", __FUNCTION__, data);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
/* If present, check credentials. See "plugin_credentials" in plugin
* See RFC 8040 section 2.5
*/
if ((authenticated = clixon_plugin_auth(h, r)) < 0)
goto done;
clicon_debug(1, "%s auth:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
/* If set but no user, we set a dummy user */
if (authenticated){
if (clicon_username_get(h) == NULL)
clicon_username_set(h, "none");
}
else{
if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
if (api_return_err(h, r, xerr, pretty, use_xml) < 0)
goto done;
goto ok;
}
goto ok;
}
clicon_debug(1, "%s auth2:%d %s", __FUNCTION__, authenticated, clicon_username_get(h));
if (restconf_stream(h, r, method, qvec, pretty, use_xml, &s) < 0)
goto done;
if (s != -1){
#ifdef STREAM_FORK
if ((pid = fork()) == 0){ /* child */
if (pvec)
free(pvec);
if (dvec)
cvec_free(dvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (xret)
xml_free(xret);
#endif /* STREAM_FORK */
/* Listen to backend socket */
if (event_reg_fd(s,
restconf_stream_cb,
(void*)r,
"stream socket") < 0)
goto done;
if (event_reg_fd(r->listen_sock,
stream_checkuplink,
(void*)r,
"stream socket") < 0)
goto done;
/* Poll upstream errors */
stream_timeout(0, (void*)r);
/* Start loop */
event_loop();
close(s);
event_unreg_fd(s, restconf_stream_cb);
event_unreg_fd(r->listen_sock, restconf_stream_cb);
event_unreg_timeout(stream_timeout, (void*)r);
clicon_exit_reset();
#ifdef STREAM_FORK
FCGX_Finish_r(r);
FCGX_Free(r, 0);
restconf_terminate(h);
exit(0);
}
/* parent */
/* Create stream_child struct and store pid and FCGI data, when child
* killed, call FCGX_Free
*/
if ((sc = malloc(sizeof(struct stream_child))) == NULL){
clicon_err(OE_XML, errno, "malloc");
goto done;
}
memset(sc, 0, sizeof(struct stream_child));
sc->sc_pid = pid;
sc->sc_r = *r;
ADDQ(sc, STREAM_CHILD);
*finish = 0; /* If spawn child, we should not finish this stream */
#endif /* STREAM_FORK */
}
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pvec)
free(pvec);
if (dvec)
cvec_free(dvec);
if (qvec)
cvec_free(qvec);
if (pcvec)
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
if (cbret)
cbuf_free(cbret);
if (xret)
xml_free(xret);
return retval;
}

View file

@ -30,21 +30,17 @@
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* XML XPATH and XSLT functions.
*/
#ifndef _CLIXON_XSL_H
#define _CLIXON_XSL_H
#ifndef _RESTCONF_STREAM_H_
#define _RESTCONF_STREAM_H_
/*
* Prototypes
*/
int xpath_vec_xsl(cxobj *cxtop, char *xpath, cxobj ***vec, size_t *veclen);
int xpath_vec_flag_xsl(cxobj *cxtop, char *xpath, uint16_t flags,
cxobj ***vec, size_t *veclen);
cxobj *xpath_first_xsl(cxobj *cxtop, char *xpath);
#ifdef COMPAT_XSL
cxobj *xpath_each(cxobj *xn_top, char *xpath, cxobj *prev);
#endif
int stream_child_free(clicon_handle h, int pid);
int stream_child_freeall(clicon_handle h);
int api_stream(clicon_handle h, FCGX_Request *r, char *streampath, int *finish);
#endif /* _CLIXON_XSL_H */
#endif /* _RESTCONF_STREAM_H_ */

108
configure vendored
View file

@ -633,9 +633,12 @@ CPP
OBJEXT
EXEEXT
ac_ct_CC
wwwuser
wwwdir
with_restconf
RANLIB
SH_SUFFIX
CLIXON_DEFAULT_CONFIG
INSTALLFLAGS
INSTALL
INSTALL_DATA
@ -683,7 +686,6 @@ infodir
docdir
oldincludedir
includedir
runstatedir
localstatedir
sharedstatedir
sysconfdir
@ -708,6 +710,7 @@ ac_user_opts='
enable_option_checking
enable_debug
with_cligen
enable_publish
with_restconf
with_configfile
'
@ -760,7 +763,6 @@ datadir='${datarootdir}'
sysconfdir='${prefix}/etc'
sharedstatedir='${prefix}/com'
localstatedir='${prefix}/var'
runstatedir='${localstatedir}/run'
includedir='${prefix}/include'
oldincludedir='/usr/include'
docdir='${datarootdir}/doc/${PACKAGE}'
@ -1013,15 +1015,6 @@ do
| -silent | --silent | --silen | --sile | --sil)
silent=yes ;;
-runstatedir | --runstatedir | --runstatedi | --runstated \
| --runstate | --runstat | --runsta | --runst | --runs \
| --run | --ru | --r)
ac_prev=runstatedir ;;
-runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
| --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
| --run=* | --ru=* | --r=*)
runstatedir=$ac_optarg ;;
-sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
ac_prev=sbindir ;;
-sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@ -1159,7 +1152,7 @@ fi
for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \
datadir sysconfdir sharedstatedir localstatedir includedir \
oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
libdir localedir mandir runstatedir
libdir localedir mandir
do
eval ac_val=\$$ac_var
# Remove trailing slashes.
@ -1312,7 +1305,6 @@ Fine tuning of the installation directories:
--sysconfdir=DIR read-only single-machine data [PREFIX/etc]
--sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com]
--localstatedir=DIR modifiable single-machine data [PREFIX/var]
--runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run]
--libdir=DIR object code libraries [EPREFIX/lib]
--includedir=DIR C header files [PREFIX/include]
--oldincludedir=DIR C header files for non-gcc [/usr/include]
@ -1346,6 +1338,8 @@ Optional Features:
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
--enable-debug Build with debug symbols, default: no
--enable-publish Enable publish of notification streams using SSE and
curl
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@ -2155,9 +2149,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
: ${INSTALLFLAGS="-s"}
CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="7"
CLIXON_VERSION_MINOR="8"
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}.PRE\""
# Fix to specific CLIgen version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
@ -2192,6 +2186,7 @@ _ACEOF
# AC_SUBST(var) makes @var@ appear in makefiles.
# clixon versions spread to Makefile's (.so files) and variable in build.c
@ -2448,7 +2443,12 @@ test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
# If yes, compile apps/restconf
wwwdir=/www-data
wwwuser=www-data
#
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
@ -3679,6 +3679,77 @@ if test "${with_cligen}"; then
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
fi
# Experimental: Curl publish notification stream to eg Nginx nchan.
# Check whether --enable-publish was given.
if test "${enable_publish+set}" = set; then :
enableval=$enable_publish;
if test "$enableval" = no; then
ac_enable_publish=no
else
ac_enable_publish=yes
fi
else
ac_enable_publish=no
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: publish is $ac_enable_publish" >&5
$as_echo "publish is $ac_enable_publish" >&6; }
if test "$ac_enable_publish" = "yes"; then
# publish streams uses libcurl
{ $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
$as_echo "#define CLIXON_PUBLISH_STREAMS 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
@ -4063,12 +4134,11 @@ fi
fi
# Set default config file location
CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml
# Check whether --with-configfile was given.
if test "${with_configfile+set}" = set; then :
withval=$with_configfile; DEFAULT_CONFIG="$withval"
else
DEFAULT_CONFIG="$(eval echo ${sysconfdir}/clixon.xml)"
withval=$with_configfile; CLIXON_DEFAULT_CONFIG="$withval"
fi
@ -4321,7 +4391,7 @@ _ACEOF
# Default location for config file
cat >>confdefs.h <<_ACEOF
#define CLIXON_DEFAULT_CONFIG "${DEFAULT_CONFIG}"
#define CLIXON_DEFAULT_CONFIG "${CLIXON_DEFAULT_CONFIG}"
_ACEOF

View file

@ -43,9 +43,9 @@ AC_INIT(lib/clixon/clixon.h.in)
: ${INSTALLFLAGS="-s"}
CLIXON_VERSION_MAJOR="3"
CLIXON_VERSION_MINOR="7"
CLIXON_VERSION_MINOR="8"
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}.PRE\""
# Fix to specific CLIgen version (eg 3.5) or head (3)
CLIGEN_VERSION="3"
@ -63,6 +63,7 @@ AC_DEFINE_UNQUOTED(CLIXON_VERSION_MINOR, $CLIXON_VERSION_MINOR, [Clixon minor re
AC_DEFINE_UNQUOTED(CLIXON_VERSION_PATCH, $CLIXON_VERSION_PATCH, [Clixon path version])
# AC_SUBST(var) makes @var@ appear in makefiles.
# clixon versions spread to Makefile's (.so files) and variable in build.c
AC_SUBST(CLIXON_VERSION)
AC_SUBST(CLIXON_VERSION_STRING)
@ -85,10 +86,13 @@ AC_SUBST(INSTALL)
AC_SUBST(INSTALL_DATA)
AC_SUBST(INSTALL_PROGRAM)
AC_SUBST(INSTALLFLAGS)
AC_SUBST(CLIXON_DEFAULT_CONFIG)
AC_SUBST(LIBS)
AC_SUBST(SH_SUFFIX)
AC_SUBST(RANLIB)
AC_SUBST(with_restconf) # If yes, compile apps/restconf
AC_SUBST(wwwdir,/www-data)
AC_SUBST(wwwuser,www-data)
#
AC_PROG_CC()
AC_PROG_CPP
@ -141,6 +145,23 @@ if test "${with_cligen}"; then
test -d "$with_cligen" && CLIGEN_PREFIX="$with_cligen"
fi
# Experimental: Curl publish notification stream to eg Nginx nchan.
AC_ARG_ENABLE(publish, AS_HELP_STRING([--enable-publish],[Enable publish of notification streams using SSE and curl]),[
if test "$enableval" = no; then
ac_enable_publish=no
else
ac_enable_publish=yes
fi
],
[ ac_enable_publish=no])
AC_MSG_RESULT(publish is $ac_enable_publish)
if test "$ac_enable_publish" = "yes"; then
# publish streams uses libcurl
AC_CHECK_LIB(curl, curl_global_init,, AC_MSG_ERROR([libcurl missing]))
AC_DEFINE(CLIXON_PUBLISH_STREAMS, 1, [Enable publish of notification streams using SSE and curl])
fi
AC_CHECK_HEADERS(cligen/cligen.h,, AC_MSG_ERROR(cligen missing. Try: git clone https://github.com/olofhagsand/cligen.git))
AC_CHECK_LIB(cligen, cligen_init,, AC_MSG_ERROR([CLIgen${CLIGEN_VERSION} missing. Try: git clone https://github.com/olofhagsand/cligen.git]))
@ -156,10 +177,10 @@ if test "x${with_restconf}" == xyes; then
fi
# Set default config file location
CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml
AC_ARG_WITH([configfile],
[AS_HELP_STRING([--with-configfile=FILE],[set default path to config file])],
[DEFAULT_CONFIG="$withval"],
[DEFAULT_CONFIG="$(eval echo ${sysconfdir}/clixon.xml)"])
[CLIXON_DEFAULT_CONFIG="$withval"],)
AC_CHECK_LIB(crypt, crypt)
AC_CHECK_HEADERS(crypt.h)
@ -184,7 +205,7 @@ CLIXON_DATADIR="${prefix}/share/clixon"
AC_DEFINE_UNQUOTED(CLIXON_DATADIR, "${CLIXON_DATADIR}", [Clixon data dir for system yang files etc])
# Default location for config file
AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${DEFAULT_CONFIG}",[Location for apps to find default config file])
AC_DEFINE_UNQUOTED(CLIXON_DEFAULT_CONFIG,"${CLIXON_DEFAULT_CONFIG}",[Location for apps to find default config file])
AH_BOTTOM([#include <clixon_custom.h>])

View file

@ -206,7 +206,7 @@ main(int argc, char **argv)
if ((yspec = yspec_new()) == NULL)
goto done;
/* Parse yang spec from given file */
if (yang_parse(h, yangdir, yangmodule, NULL, yspec) < 0)
if (yang_parse(h, NULL, yangmodule, yangdir, NULL, yspec, NULL) < 0)
goto done;
/* Set database directory option */
if (xmldb_setopt(h, "dbdir", dbdir) < 0)

View file

@ -211,7 +211,7 @@ append_listkeys(cbuf *ckey,
xml_name(xt), keyname);
goto done;
}
if (uri_percent_encode(xml_body(xkey), &bodyenc) < 0)
if (uri_percent_encode(&bodyenc, "%s", xml_body(xkey)) < 0)
goto done;
if (i++)
cprintf(ckey, ",");
@ -328,7 +328,7 @@ get(char *dbname,
* If xml element is a leaf-list, then the next element is expected to
* be a value
*/
if (uri_percent_decode(restval, &argdec) < 0)
if (uri_percent_decode(&argdec, restval) < 0)
goto done;
if ((xc = xml_find(x, name))==NULL ||
(xb = xml_find(xc, argdec))==NULL){
@ -681,7 +681,7 @@ put(char *dbfile,
goto done;
break;
case Y_LEAF_LIST:
if (uri_percent_encode(body, &bodyenc) < 0)
if (uri_percent_encode(&bodyenc, "%s", body) < 0)
goto done;
cprintf(cbxk, "=%s", bodyenc);
break;

View file

@ -125,7 +125,7 @@ text_db2file(struct text_handle *th,
char **filename)
{
int retval = -1;
cbuf *cb;
cbuf *cb = NULL;
char *dir;
if ((cb = cbuf_new()) == NULL){
@ -174,7 +174,6 @@ text_connect(void)
xh = (xmldb_handle)th;
done:
return xh;
}
/*! Disconnect from to a datastore plugin and deallocate handle
@ -191,6 +190,7 @@ text_disconnect(xmldb_handle xh)
size_t klen;
int i;
clicon_debug(1, "%s", __FUNCTION__);
if (th){
if (th->th_dbdir)
free(th->th_dbdir);
@ -892,7 +892,7 @@ text_put(xmldb_handle xh,
}
cbretlocal++;
}
if ((yspec = th->th_yangspec) == NULL){
if ((yspec = th->th_yangspec) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
@ -1387,7 +1387,7 @@ main(int argc,
db_init(db);
if ((yspec = yspec_new()) == NULL)
goto done
if (yang_parse(h, yangdir, yangmod, NULL, yspec) < 0)
if (yang_parse(h, NULL, yangmod, yangdir, NULL, yspec) < 0)
goto done;
if (strcmp(cmd, "get")==0){
if (argc < 5)

View file

@ -2,17 +2,18 @@
## What is Clixon?
Clixon is a configuration management tool including a generated CLI ,
Yang parser, netconf and restconf interface and an embedded databases.
Clixon is a YANG-based configuration manager, with interactive CLI,
NETCONF and RESTCONF interfaces, an embedded database and transaction
support.
## Why should I use Clixon?
If you want an easy-to-use configuration frontend based on yang with an
If you want an easy-to-use configuration toolkit based on yang with an
open-source license. Typically for embedded devices requiring a
config interface such as routers and switches.
## What license is available?
CLIXON is dual license. Either Apache License, Version 2.0 or GNU
Clixon is dual license. Either Apache License, Version 2.0 or GNU
General Public License Version 2.
## Is Clixon extendible?
@ -41,9 +42,19 @@ The example:
sudo make install
```
## How do you run Clixon example commands?
- Start a backend server: `clixon_backend -Ff /usr/local/etc/example.xml`
- Start a cli session: `clixon_cli -f /usr/local/etc/example.xml`
- Start a netconf session: `clixon_netconf -f /usr/local/etc/example.xml`
- Start a restconf daemon: `sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data`
- Send a restconf command: `curl -G http://127.0.0.1/restconf/data`
More info in the [example](../example) directory.
## Do I need to setup anything? (IMPORTANT)
The config demon requires a valid group to create a server UNIX socket.
The config demon requires a valid group to create a server UNIX domain socket.
Define a valid CLICON_SOCK_GROUP in the config file or via the -g option
or create the group and add the user to it. The default group is 'clicon'.
Add yourself and www-data, if you intend to use restconf.
@ -63,18 +74,11 @@ clicon:x:1001:<user>,www-data
## What about reference documentation?
Clixon uses Doxygen for reference documentation.
Build using 'make doc' and aim your browser at doc/html/index.html or
use the web resource: http://clicon.org/ref/index.html
## How do you run the example?
- Start a backend server: 'clixon_backend -Ff /usr/local/etc/example.xml'
- Start a cli session: clixon_cli -f /usr/local/etc/example.xml
- Start a netconf session: clixon_netconf -f /usr/local/etc/example.xml
Build using 'make doc' and aim your browser at doc/html/index.html.
## How is configuration data stored?
Configuration data is stored in an XML datastore. The default is a
text-based datastore. In the example the datastore are regular files found in
/usr/local/var/example/.
Configuration data is stored in an XML datastore. In the example the
datastore are regular files found in /usr/local/var/example/.
## What is validate and commit?
Clixon follows netconf in its validate and commit semantics.
@ -93,20 +97,36 @@ configuration file is /usr/local/etc/clixon.xml. The example
configuration file is installed at /usr/local/etc/example.xml. The
YANG specification for the configuration file is clixon-config.yang.
You can change where CLixon looks for the configuration FILE as follows:
You can change where Clixon looks for the configuration FILE as follows:
- Provide -f FILE option when starting a program (eg clixon_backend -f FILE)
- Provide --with-configfile=FILE when configuring
- Provide --with-sysconfig=<dir> when configuring, then FILE is <dir>/clixon.xml
- Provide --sysconfig=<dir> when configuring then FILE is <dir>/etc/clixon.xml
- FILE is /usr/local/etc/clixon.xml
## How do I enable Yang features?
Yang models have features, and parts of a specification can be
conditional using the if-feature statement. In Clixon, features are
enabled in the configuration file using <CLICON_FEATURE>.
The example below shows enabling a specific feature; enabling all features in module; and enabling all features in all modules, respectively:
```
<CLICON_FEATURE>ietf-routing:router-id</CLICON_FEATURE>
<CLICON_FEATURE>ietf-routing:*</CLICON_FEATURE>
<CLICON_FEATURE>*:*</CLICON_FEATURE>
```
Features can be probed by using RFC 7895 Yang module library which provides
information on all modules and which features are enabled.
## Can I run Clixon as docker containers?
Yes, the example works as docker containers as well. There should be a
prepared container in docker hib for the example where the backend and
prepared container in docker hub for the example where the backend and
CLI is bundled.
```
sudo docker run -ti --rm olofhagsand/clixon_example
sudo docker run -td olofhagsand/clixon_example
```
Look in the example documentation for more info.
@ -114,7 +134,9 @@ Look in the example documentation for more info.
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
```
echo "<rpc><get-config><source><candidate/></source><configuration/></get-config></rpc>]]>]]>" | clixon_netconf -f /usr/local/etc/example.xml
```
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
@ -139,7 +161,6 @@ For example, using nginx, install, and edit config file: /etc/nginx/sites-availa
server {
...
location /restconf {
root /usr/share/nginx/html/restconf;
fastcgi_pass unix:/www-data/fastcgi_restconf.sock;
include fastcgi_params;
}
@ -150,8 +171,6 @@ Start nginx daemon
sudo /etc/init.d/nginx start
```
Read more in the restconf docs.
Example:
```
curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type
@ -161,24 +180,38 @@ Example:
}
]
```
Read more in the (restconf)[../apps/restconf] docs.
## How do I use notifications?
The example has a prebuilt notification stream called "ROUTING" that triggers every 10s.
You enable the notification either via the cli:
## Does Clixon support event streams?
Yes, Clixon supports event notification streams in the CLI, Netconf and Restconf API:s.
The example has a prebuilt notification stream called "EXAMPLE" that triggers every 5s.
You enable the notification via the CLI:
```
cli> notify
cli>
```
or via netconf:
```
clixon_netconf -qf /usr/local/etc/example.xml
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
event-class fault;
reportingEntity {
card Ethernet0;
}
severity major;
...
```
or via NETCONF:
```
clixon_netconf -qf /usr/local/etc/example.xml
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"><eventTime>2018-09-30T12:44:59.657276</eventTime><event xmlns="http://example.com/event/1.0"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event></notification>]]>]]>
...
```
or via restconf:
```
curl -H "Accept: text/event-stream" -s -X GET http://localhost/streams/EXAMPLE
```
Consult (../apps/restconf/README.md) on more information on how to setup a reverse proxy for restconf streams. It is also possible to configure a pub/sub system such as (Nginx Nchan)[https://nchan.io].
## How should I start the backend daemon?

View file

@ -40,16 +40,13 @@ RUN apt-get update && apt-get install -y \
libfcgi-dev \
libcurl4-openssl-dev
RUN groupadd clicon
# Create a directory to hold source-code, dependencies etc
RUN mkdir /clixon
WORKDIR /clixon
# Clone cligen and clixon
RUN git clone https://github.com/olofhagsand/cligen.git
RUN git clone https://github.com/clicon/clixon.git
RUN git clone -b develop https://github.com/clicon/clixon.git
# Build cligen
WORKDIR /clixon/cligen
@ -59,12 +56,13 @@ RUN make install
# Build clixon
WORKDIR /clixon/clixon
RUN git checkout -b develop origin/develop
RUN ./configure
RUN make
RUN make install
RUN make install-include
RUN rm -rf /clixon
RUN ldconfig

View file

@ -47,7 +47,7 @@ SHELL = /bin/sh
.PHONY: all clean depend install docker push
all:
@echo "Run make docker to build docker image"
echo "Run make docker to build docker image"
clean:
@ -56,6 +56,7 @@ distclean: clean
docker:
sudo docker build -t $(IMAGE) .
echo "cd ../example; make docker to build example application"
push:
sudo docker push $(IMAGE)

View file

@ -6,7 +6,7 @@ the IMAGE in Makefile.in and push it to another name.
The clixon docker image is a base image that can be used to build
clixon applications. It has all the whole code for a clixon release
which it downloads from git - it does not use local code.
which it downloads from git - it does not use local code (note it may even use develop branch).
See example/docker for how to build a docker application using the base image.
@ -16,4 +16,4 @@ Perform the build by 'make docker'.
You may also do 'make push' if you want to push the image, but you may then consider changing the image name (in the makefile:s).
You may run the container directly by going directly to example and
the docker runtime scripts there
the docker runtime scripts there

View file

@ -34,8 +34,11 @@
FROM olofhagsand/clixon
MAINTAINER Olof Hagsand <olof@hagsand.se>
RUN apt-get update && apt-get install -y \
procps # ps for debugging
#RUN apt-get update && apt-get install -y procps # ps for debugging
# The example uses "clicon" group
RUN groupadd clicon
# Create a directory to hold source-code, dependencies etc
RUN mkdir /example
WORKDIR /example
@ -53,7 +56,8 @@ RUN make
RUN make install
RUN install example.xml /usr/local/etc/clixon.xml
CMD /usr/local/sbin/clixon_backend && /usr/local/bin/clixon_cli
# Log to stderr. Add -D 1 for debug
CMD /usr/local/sbin/clixon_backend -F -a IPv4 -u 0.0.0.0 -s init -l e

View file

@ -43,6 +43,8 @@ localstatedir = @localstatedir@
libdir = @exec_prefix@/lib
APPNAME = example
# Install here if you want default clixon location:
CLIXON_DEFAULT_CONFIG = @CLIXON_DEFAULT_CONFIG@
CC = @CC@
CFLAGS = @CFLAGS@ -rdynamic -fPIC
@ -56,6 +58,7 @@ CLI_PLUGIN = $(APPNAME)_cli.so
NETCONF_PLUGIN = $(APPNAME)_netconf.so
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
# Example docker image. PLEASE CHANGE THIS
IMAGE = olofhagsand/clixon_example
@ -120,6 +123,7 @@ distclean: clean
install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(BE2_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) $(APPNAME).xml
install -d -m 0755 $(DESTDIR)$(sysconfdir)
install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir)
# install -m 0644 $(APPNAME).xml $(DESTDIR)$(CLIXON_DEFAULT_CONFIG)
install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
install -m 0644 $(YANGSPECS) $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli

View file

@ -41,7 +41,7 @@ Send netconf command:
```
Start clixon restconf daemon
```
> sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data
```
Send restconf command
```
@ -76,12 +76,12 @@ Send restconf command
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
```
## Creating notification
## Streams
The example has an example notification triggering every 10s. To start a notification
stream in the session, create a subscription:
The example has an EXAMPLE stream notification triggering every 5s. To start a notification
stream in the session using netconf, create a subscription:
```
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc><create-subscription><stream>EXAMPLE</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
@ -95,6 +95,8 @@ Routing notification
...
```
Restconf support is also supported, see [../apps/restconf/README.md].
## Initializing a plugin
The example includes a restonf, netconf, CLI and two backend plugins.
@ -198,19 +200,13 @@ The example contains some stubs for authorization according to [RFC8341(NACM)](h
Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example.
## Run as docker container
(Note not updated)
```
cd docker
# look in README
```
## Docker
Run the example as a docker container as follows:
Run the example as a docker container and access it from a host CLI as follows:
```
sudo docker run -ti --rm olofhagsand/clixon_example
ID=$(sudo docker run -td olofhagsand/clixon_example)
IP=$(sudo docker inspect -f '{{.NetworkSettings.IPAddress }}' $ID)
clixon_cli -a IPv4 -u $IP -f ./example.xml
```
Build the container and push yourself: First change the IMAGE variable in Makefile (eg to "you/clixon_example). Then build and push:
@ -220,5 +216,9 @@ make push
sudo docker run -ti --rm you/clixon_example
```
Note that the configuration database is internal in the container, so
it is deleted if the container is restarted. To make the configuration
database persistent, you need to mount running_db using `-v`

View file

@ -1,5 +1,6 @@
<config>
<CLICON_CONFIGFILE>/usr/local/etc/example.xml</CLICON_CONFIGFILE>
<CLICON_FEATURE>*:*</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/example/yang</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
<CLICON_CLI_MODE>example</CLICON_CLI_MODE>

View file

@ -22,12 +22,38 @@ module example {
base if:interface-type;
}
/* Translation function example - See also example_cli */
list translate{
leaf value{
type string;
}
}
/* State data (not config) for the example application*/
container state {
config false;
description "state data for the example application (must be here for example get operation)";
leaf-list op {
type string;
}
}
/* Example notification as used in RFC 5277 and RFC 8040 */
notification event {
description "Example notification event.";
leaf event-class {
type string;
description "Event class identifier.";
}
container reportingEntity {
description "Event specific information.";
leaf card {
type string;
description "Line card identifier.";
}
}
leaf severity {
type string;
description "Event severity description.";
}
}
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.

View file

@ -31,17 +31,14 @@
***** END LICENSE BLOCK *****
*
* IETF yang routing example
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <assert.h>
#include <sys/time.h>
/* clicon */
@ -54,7 +51,7 @@
#include <clixon/clixon_backend.h>
/* forward */
static int notification_timer_setup(clicon_handle h);
static int example_stream_timer_setup(clicon_handle h);
/*! This is called on validate (and commit). Check validity of candidate
*/
@ -93,32 +90,33 @@ transaction_commit(clicon_handle h,
/*! Routing example notifcation timer handler. Here is where the periodic action is
*/
static int
notification_timer(int fd,
void *arg)
example_stream_timer(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
if (backend_notify(h, "ROUTING", 0, "Routing notification") < 0)
/* XXX Change to actual netconf notifications */
if (stream_notify(h, "EXAMPLE", "<event><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
goto done;
if (notification_timer_setup(h) < 0)
if (example_stream_timer_setup(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Set up routing notifcation timer
/*! Set up example stream notification timer
*/
static int
notification_timer_setup(clicon_handle h)
example_stream_timer_setup(clicon_handle h)
{
struct timeval t, t1;
gettimeofday(&t, NULL);
t1.tv_sec = 10; t1.tv_usec = 0;
t1.tv_sec = 5; t1.tv_usec = 0;
timeradd(&t, &t1, &t);
return event_reg_timeout(t, notification_timer, h, "notification timer");
return event_reg_timeout(t, example_stream_timer, h, "example stream timer");
}
/*! IETF Routing fib-route rpc
@ -176,8 +174,15 @@ empty(clicon_handle h, /* Clicon handle */
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get
* @note this example code returns a static statedata used in testing.
* Real code would poll state
* @note this example code returns requires this yang snippet:
container state {
config false;
description "state data for example application";
leaf-list op {
type string;
}
}
*
*/
int
example_statedata(clicon_handle h,
@ -187,12 +192,13 @@ example_statedata(clicon_handle h,
int retval = -1;
cxobj **xvec = NULL;
/* Example of (static) statedata, real code would poll state */
if (xml_parse_string("<interfaces-state><interface>"
"<name>eth0</name>"
"<type>ex:eth</type>"
"<if-index>42</if-index>"
"</interface></interfaces-state>", NULL, &xstate) < 0)
/* Example of (static) statedata, real code would poll state
* Note this state needs to be accomanied by yang snippet
* above
*/
if (xml_parse_string("<state>"
"<op>42</op>"
"</state>", NULL, &xstate) < 0)
goto done;
retval = 0;
done:
@ -221,8 +227,8 @@ example_reset(clicon_handle h,
cxobj *xt = NULL;
if (xml_parse_string("<config><interfaces><interface>"
"<name>lo</name><type>ex:loopback</type>"
"</interface></interfaces></config>", NULL, &xt) < 0)
"<name>lo</name><type>ex:loopback</type>"
"</interface></interfaces></config>", NULL, &xt) < 0)
goto done;
/* Replace parent w fiorst child */
if (xml_rootchild(xt, 0, &xt) < 0)
@ -257,13 +263,19 @@ example_start(clicon_handle h,
return 0;
}
int
example_exit(clicon_handle h)
{
return 0;
}
clixon_plugin_api *clixon_plugin_init(clicon_handle h);
static clixon_plugin_api api = {
"example", /* name */
clixon_plugin_init, /* init - must be called clixon_plugin_init */
example_start, /* start */
NULL, /* exit */
example_exit, /* exit */
.ca_reset=example_reset, /* reset */
.ca_statedata=example_statedata, /* statedata */
.ca_trans_begin=NULL, /* trans begin */
@ -282,9 +294,27 @@ static clixon_plugin_api api = {
clixon_plugin_api *
clixon_plugin_init(clicon_handle h)
{
struct timeval retention = {0,0};
clicon_debug(1, "%s backend", __FUNCTION__);
if (notification_timer_setup(h) < 0)
/* Example stream initialization:
* 1) Register EXAMPLE stream
* 2) setup timer for notifications, so something happens on stream
* 3) setup stream callbacks for notification to push channel
*/
if (clicon_option_exists(h, "CLICON_STREAM_RETENTION"))
retention.tv_sec = clicon_option_int(h, "CLICON_STREAM_RETENTION");
if (stream_add(h, "EXAMPLE", "Example event stream", 1, &retention) < 0)
goto done;
/* Enable nchan pub/sub streams
* assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
*/
if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
stream_publish(h, "EXAMPLE") < 0)
goto done;
if (example_stream_timer_setup(h) < 0)
goto done;
/* Register callback for routing rpc calls */
if (rpc_callback_register(h, fib_route,
NULL,
@ -301,6 +331,7 @@ clixon_plugin_init(clicon_handle h)
"empty"/* Xml tag when callback is made */
) < 0)
goto done;
/* Return plugin API */
return &api;
done:

View file

@ -44,6 +44,7 @@
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/syslog.h>
/* clicon */
#include <cligen/cligen.h>
@ -119,7 +120,7 @@ clixon_plugin_init(clicon_handle h)
clicon_debug(1, "%s backend nacm", __FUNCTION__);
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (nacm_mode==NULL || strcmp(nacm_mode, "disabled") == 0){
clicon_debug(1, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
clicon_log(LOG_WARNING, "%s CLICON_NACM_MODE not enabled: example nacm module disabled", __FUNCTION__);
return NULL;
}
return &api;

View file

@ -68,11 +68,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
/* Show eth0 interfaces config using XPATH */
if (clicon_rpc_get_config(h, "running",
#ifdef COMPAT_XSL
"/interfaces/interface[name=eth0]",
#else
"/interfaces/interface[name='eth0']",
#endif
&xret) < 0)
goto done;

View file

@ -64,7 +64,7 @@ load("Load configuration from XML file") <filename:string>("Filename (local file
}
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
rpc("ex:fib-route rpc") <instance:string>("routing instance"), fib_route_rpc("myarg");
notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text");
no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml");
notify("Get notifications from backend"), cli_notify("EXAMPLE", "1", "text");
no("Negate") notify("Get notifications from backend"), cli_notify("EXAMPLE", "0", "xml");
lock,cli_lock("candidate");
unlock,cli_unlock("candidate");

View file

@ -39,7 +39,7 @@
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <fcgi_stdio.h>
#include <fcgiapp.h>
#include <curl/curl.h>
/* cligen */

View file

@ -6,6 +6,9 @@
/* Location for apps to find default config file */
#undef CLIXON_DEFAULT_CONFIG
/* Enable publish of notification streams using SSE and curl */
#undef CLIXON_PUBLISH_STREAMS
/* Clixon major release */
#undef CLIXON_VERSION_MAJOR

View file

@ -43,11 +43,6 @@
int strverscmp (__const char *__s1, __const char *__s2);
#endif
/* Set if you want to enable "v" cli callback functions, such as cli_setv()
* This was obsoleted in 3.7
*/
#undef COMPAT_CLIV
/* Set if you want to assert that all rpc messages have set username
*/
#undef RPC_USERNAME_ASSERT
@ -56,13 +51,3 @@ int strverscmp (__const char *__s1, __const char *__s2);
*/
#define XMLNS_YANG_ONLY 1
/* Set if you want to enable old xpath functions in clixon_xsl.* instead of the
* the new xpath functions in clixon_xpath.*
* Note that when changing from old xpath code to new, calls on the form
* `x[a=str]` where `str` is a string (not a number or XML symbol),
* must be changed to: `x[a='str'] or x[a="str"]`
* Enabling COMPAT_XSL may make sense if you have written a lot of user code that
* relieson the error above. Or if a bug appears in the newimplementation.
* @see test/lib.sh
*/
#undef COMPAT_XSL

View file

@ -63,11 +63,11 @@
#define LIBCLIXON_API 1
#include <clixon/clixon_sig.h>
#include <clixon/clixon_log.h>
#include <clixon/clixon_err.h>
#include <clixon/clixon_queue.h>
#include <clixon/clixon_hash.h>
#include <clixon/clixon_handle.h>
#include <clixon/clixon_log.h>
#include <clixon/clixon_yang.h>
#include <clixon/clixon_yang_type.h>
#include <clixon/clixon_event.h>
@ -75,13 +75,14 @@
#include <clixon/clixon_file.h>
#include <clixon/clixon_xml.h>
#include <clixon/clixon_xml_sort.h>
#include <clixon/clixon_yang_module.h>
#include <clixon/clixon_stream.h>
#include <clixon/clixon_proto.h>
#include <clixon/clixon_proto_client.h>
#include <clixon/clixon_plugin.h>
#include <clixon/clixon_options.h>
#include <clixon/clixon_xml_map.h>
#include <clixon/clixon_xml_db.h>
#include <clixon/clixon_xsl.h>
#include <clixon/clixon_xpath_ctx.h>
#include <clixon/clixon_xpath.h>
#include <clixon/clixon_json.h>

View file

@ -55,7 +55,7 @@ enum clicon_err{
OE_DB = 1, /* database registries */
OE_DEMON, /* demons: pidfiles, etc */
OE_EVENTS, /* events, filedescriptors, timeouts */
OE_CFG, /* config commit / quagga */
OE_CFG, /* configuration */
OE_PROTO, /* config/client communication */
OE_REGEX, /* Regexp error */
OE_UNIX, /* unix/linux syscall error */

View file

@ -43,6 +43,8 @@
*/
int clicon_exit_set(void);
int clicon_exit_reset(void);
int clicon_exit_get(void);
int event_reg_fd(int fd, int (*fn)(int, void*), void *arg, char *str);

View file

@ -74,4 +74,10 @@ clicon_hash_t *clicon_options(clicon_handle h);
/* Return internal clicon data (hash-array) given a handle.*/
clicon_hash_t *clicon_data(clicon_handle h);
/* Return internal stream hash-array given a handle.*/
struct event_stream *clicon_stream(clicon_handle h);
struct event_stream;
int clicon_stream_set(clicon_handle h, struct event_stream *es);
int clicon_stream_append(clicon_handle h, struct event_stream *es);
#endif /* _CLIXON_HANDLE_H_ */

View file

@ -44,24 +44,21 @@
#define CLICON_LOG_SYSLOG 1 /* print logs on syslog */
#define CLICON_LOG_STDERR 2 /* print logs on stderr */
#define CLICON_LOG_STDOUT 4 /* print logs on stdout */
/*
* Types
*/
typedef int (clicon_log_notify_t)(int level, char *msg, void *arg);
#define CLICON_LOG_FILE 8 /* print logs on clicon_log_filename */
/*
* Variables
*/
extern int debug;
/*
* Prototypes
*/
int clicon_log_init(char *ident, int upto, int flags);
int clicon_log_exit(void);
int clicon_log_opt(char c);
int clicon_log_file(char *filename);
int clicon_get_logflags(void);
int clicon_log_str(int level, char *msg);
#if defined(__GNUC__) && __GNUC__ >= 3
int clicon_log(int level, char *format, ...) __attribute__ ((format (printf, 2, 3)));
int clicon_debug(int dbglevel, char *format, ...) __attribute__ ((format (printf, 2, 3)));
@ -69,9 +66,7 @@ int clicon_debug(int dbglevel, char *format, ...) __attribute__ ((format (printf
int clicon_log(int level, char *format, ...);
int clicon_debug(int dbglevel, char *format, ...);
#endif
clicon_log_notify_t *clicon_log_register_callback(clicon_log_notify_t *cb, void *arg);
int clicon_debug_init(int dbglevel, FILE *f);
char *mon2name(int md);
#endif /* _CLIXON_LOG_H_ */

View file

@ -62,5 +62,7 @@ int netconf_operation_failed(cbuf *cb, char *type, char *message);
int netconf_operation_failed_xml(cxobj **xret, char *type, char *message);
int netconf_malformed_message(cbuf *cb, char *message);
int netconf_malformed_message_xml(cxobj **xret, char *message);
int netconf_trymerge(cxobj *x, yang_spec *yspec, cxobj **xret);
int netconf_module_load(clicon_handle h);
#endif /* _CLIXON_NETCONF_LIB_H */

View file

@ -81,7 +81,8 @@ enum startup_mode_t{
/* Print registry on file. For debugging. */
void clicon_option_dump(clicon_handle h, int dblevel);
/* Initialize options: set defaults, read config-file, etc */
int clicon_options_main(clicon_handle h);
int clicon_options_main(clicon_handle h, yang_spec *yspec);
/*! Check if a clicon option has a value */
int clicon_option_exists(clicon_handle h, const char *name);
@ -164,11 +165,13 @@ int clicon_quiet_mode_set(clicon_handle h, int val);
yang_spec * clicon_dbspec_yang(clicon_handle h);
int clicon_dbspec_yang_set(clicon_handle h, struct yang_spec *ys);
char *clicon_dbspec_name(clicon_handle h);
int clicon_dbspec_name_set(clicon_handle h, char *name);
#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */
yang_spec * clicon_config_yang(clicon_handle h);
int clicon_config_yang_set(clicon_handle h, struct yang_spec *ys);
#endif
yang_spec *clicon_netconf_yang(clicon_handle h);
int clicon_netconf_yang_set(clicon_handle h, struct yang_spec *ys);
cxobj *clicon_conf_xml(clicon_handle h);
int clicon_conf_xml_set(clicon_handle h, cxobj *x);
plghndl_t clicon_xmldb_plugin_get(clicon_handle h);
int clicon_xmldb_plugin_set(clicon_handle h, plghndl_t handle);

View file

@ -87,7 +87,7 @@ int clicon_msg_send(int s, struct clicon_msg *msg);
int clicon_msg_rcv(int s, struct clicon_msg **msg, int *eof);
int send_msg_notify(int s, int level, char *event);
int send_msg_notify_xml(int s, cxobj *xev);
int send_msg_reply(int s, char *data, uint32_t datalen);

View file

@ -37,17 +37,30 @@
#ifndef _CLIXON_QUEUE_H_
#define _CLIXON_QUEUE_H_
/*
* Circular queue structure for use as first entry in a parent structure.
/*! Circular queue structure for use as first entry in a parent structure.
* Add qelem_t as first element in struct
* @code
* struct a{
* qelem_t a_q; # this must be there
* int a_b; # other elements
* ...
* };
* @endcode
*/
typedef struct _qelem_t {
struct _qelem_t *q_next;
struct _qelem_t *q_prev;
} qelem_t;
/*
* Append element 'elem' to queue.
*/
/*! Append element 'elem' to queue.
* @param[in] elem Element to be added
* @param[in,out] pred Add element after this
* @code
* struct a *list; # existing list
* struct a *new = malloc(...);
* ADDQ(new, list);
* @endcode
*/
#define ADDQ(elem, pred) { \
register qelem_t *Xe = (qelem_t *) (elem); \
register qelem_t *Xp = (qelem_t *) (pred); \
@ -62,8 +75,14 @@ typedef struct _qelem_t {
} \
}
/*
* Insert element 'elem' in queue after 'pred'
/*! Insert element 'elem' in queue after 'pred'
* @param[in] elem Element to be added
* @param[in,out] pred Add element after this
* @code
* struct a *list; # existing list
* struct a *new = malloc(...);
* INSQ(new, list);
* @endcode
*/
#define INSQ(elem, pred) { \
register qelem_t *Xe = (qelem_t *) (elem); \
@ -79,9 +98,16 @@ typedef struct _qelem_t {
pred = elem; \
}
/*
* Remove element 'elem' from queue. 'head' is the pointer to the queue and
/*! Remove element 'elem' from queue. 'head' is the pointer to the queue and
* is of 'type'.
* @param[in] elem
* @param[in] head
* @param[in] type XXX needed?
* @code
* struct a *list; # existing list
* struct a *el; # remove this
* DELQ(el, list, struct a*);
* @endcode
*/
#define DELQ(elem, head, type) { \
register qelem_t *Xe = (qelem_t *) elem; \
@ -92,10 +118,13 @@ typedef struct _qelem_t {
head = (type)Xe->q_next; \
}
/*
* Get next entry in list
/*! Get next entry in list
* @param[in] type Type of element
* @param[in] el Return next element after elem.
* @code
* struct a *list; # existing element (or list)
* NEXTQ(struct a*, el);
*/
#define NEXTQ(type, elem) ((type)((elem)?((qelem_t *)(elem))->q_next:NULL))
#endif /* _CLIXON_QUEUE_H_ */

116
lib/clixon/clixon_stream.h Normal file
View file

@ -0,0 +1,116 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 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 *****
* Event notification streams according to RFC5277
*/
#ifndef _CLIXON_STREAM_H_
#define _CLIXON_STREAM_H_
/*
* Types
*/
/* Subscription callback
* @param[in] h Clicon handle
* @param[in] op Operation: 0 OK, 1 Close
* @param[in] event Event as XML
* @param[in] arg Extra argument provided in stream_ss_add
* @see stream_ss_add
*/
typedef int (*stream_fn_t)(clicon_handle h, int op, cxobj *event, void *arg);
struct stream_subscription{
qelem_t ss_q; /* queue header */
char *ss_stream; /* Name of associated stream */
char *ss_xpath; /* Filter selector as xpath */
struct timeval ss_starttime; /* Replay starttime */
struct timeval ss_stoptime; /* Replay stoptime */
stream_fn_t ss_fn; /* Callback when event occurs */
void *ss_arg; /* Callback argument */
};
/* Replay time-series */
struct stream_replay{
qelem_t r_q; /* queue header */
struct timeval r_tv; /* time index */
cxobj *r_xml; /* event in xml form */
};
/* See RFC8040 9.3, stream list, no replay support for now
*/
struct event_stream{
qelem_t es_q; /* queue header */
char *es_name; /* name of notification event stream */
char *es_description;
struct stream_subscription *es_subscription;
int es_replay_enabled; /* set if replay is enables */
struct timeval es_retention; /* replay retention - how much to save */
struct stream_replay *es_replay;
};
typedef struct event_stream event_stream_t;
/*
* Prototypes
*/
event_stream_t *stream_find(clicon_handle h, const char *name);
int stream_add(clicon_handle h, const char *name, const char *description, int replay_enabled, struct timeval *retention);
int stream_delete_all(clicon_handle h);
int stream_get_xml(clicon_handle h, int access, cbuf *cb);
int stream_timer_setup(int fd, void *arg);
/* Subscriptions */
struct stream_subscription *stream_ss_add(clicon_handle h, char *stream,
char *xpath, struct timeval *start, struct timeval *stop,
stream_fn_t fn, void *arg);
int stream_ss_rm(clicon_handle h, event_stream_t *es, struct stream_subscription *ss);
struct stream_subscription *stream_ss_find(event_stream_t *es,
stream_fn_t fn, void *arg);
int stream_ss_delete_all(clicon_handle h, stream_fn_t fn, void *arg);
int stream_ss_delete(clicon_handle h, char *name, stream_fn_t fn, void *arg);
int stream_notify_xml(clicon_handle h, char *stream, cxobj *xml);
#if defined(__GNUC__) && __GNUC__ >= 3
int stream_notify(clicon_handle h, char *stream, const char *event, ...) __attribute__ ((format (printf, 3, 4)));
#else
int stream_notify(clicon_handle h, char *stream, const char *event, ...);
#endif
/* Replay */
int stream_replay_add(event_stream_t *es, struct timeval *tv, cxobj *xv);
int stream_replay_trigger(clicon_handle h, char *stream, stream_fn_t fn, void *arg);
/* Experimental publish streams using SSE. CLIXON_PUBLISH_STREAMS should be set */
int stream_publish(clicon_handle h, char *stream);
int stream_publish_init();
int stream_publish_exit();
#endif /* _CLIXON_STREAM_H_ */

View file

@ -76,9 +76,14 @@ static inline char * strdup4(char *str)
char **clicon_strsep(char *string, char *delim, int *nvec0);
char *clicon_strjoin (int argc, char **argv, char *delim);
int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
int uri_percent_encode(char *str, char **escp);
int uri_percent_decode(char *esc, char **str);
int xml_chardata_encode(char *str, char **escp);
#if defined(__GNUC__) && __GNUC__ >= 3
int uri_percent_encode(char **encp, char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
int xml_chardata_encode(char **escp, char *fmt, ... ) __attribute__ ((format (printf, 2, 3)));
#else
int uri_percent_encode(char **encp, char *str, ...);
int xml_chardata_encode(char **escp, char *fmt, ...);
#endif
int uri_percent_decode(char *enc, char **str);
const char *clicon_int2str(const map_str2int *mstab, int i);
int clicon_str2int(const map_str2int *mstab, char *str);

View file

@ -44,9 +44,9 @@ extern int xml_child_sort;
/*
* Prototypes
*/
int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp);
int xml_cmp(const void* arg1, const void* arg2);
int xml_sort(cxobj *x0, void *arg);
int xml_child_spec(char *name, cxobj *xp, yang_spec *yspec, yang_stmt **yp);
int xml_cmp(const void* arg1, const void* arg2);
int xml_sort(cxobj *x0, void *arg);
cxobj *xml_search(cxobj *x, char *name, int yangi, enum rfc_6020 keyword, int keynr, char **keyvec, char **keyval);
int xml_insert_pos(cxobj *x0, char *name, int yangi, enum rfc_6020 keyword,
int keynr, char **keyvec, char **keyval, int low,

View file

@ -57,7 +57,8 @@
* - Cant use the symbols in this file because yacc needs token definitions
*/
enum rfc_6020{
Y_ANYXML = 0,
Y_ACTION = 0,
Y_ANYXML,
Y_ARGUMENT,
Y_AUGMENT,
Y_BASE,
@ -258,9 +259,11 @@ char *yang_find_myprefix(yang_stmt *ys);
int yang_order(yang_stmt *y);
int yang_print(FILE *f, yang_node *yn);
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
int ys_populate(yang_stmt *ys, void *arg);
yang_stmt *yang_parse_file(int fd, const char *name, yang_spec *ysp);
int yang_parse(clicon_handle h, const char *yang_dir,
const char *module, const char *revision, yang_spec *ysp);
int yang_parse(clicon_handle h, const char *filename,
const char *module, const char *dir,
const char *revision, yang_spec *ysp, yang_stmt **ymodp);
int yang_apply(yang_node *yn, enum rfc_6020 key, yang_applyfn_t fn,
void *arg);
int yang_abs_schema_nodeid(yang_spec *yspec, char *schema_nodeid,
@ -271,8 +274,8 @@ cg_var *ys_parse(yang_stmt *ys, enum cv_type cvtype);
int ys_parse_sub(yang_stmt *ys, char *extra);
int yang_mandatory(yang_stmt *ys);
int yang_config(yang_stmt *ys);
yang_spec *yang_spec_netconf(clicon_handle h);
yang_spec *yang_spec_main(clicon_handle h);
int yang_spec_parse_module(clicon_handle h, char *module, char *dir, char *revision, yang_spec *yspec, yang_stmt **ymodp);
int yang_spec_parse_file(clicon_handle h, char *filename, char *dir, yang_spec *yspec, yang_stmt **ymodp);
cvec *yang_arg2cvec(yang_stmt *ys, char *delimi);
int yang_key_match(yang_node *yn, char *name);

View file

@ -0,0 +1,57 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 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 *****
* Yang module and feature handling
* @see https://tools.ietf.org/html/rfc7895
*/
#ifndef _CLIXON_YANG_MODULE_H_
#define _CLIXON_YANG_MODULE_H_
/*
* Constants
*/
/*
* Types
*/
/*
* Prototypes
*/
int yang_modules_init(clicon_handle h);
char *yang_modules_revision(clicon_handle h);
int yang_modules_state_get(clicon_handle h, yang_spec *yspec, cxobj **xret);
#endif /* _CLIXON_YANG_MODULE_H_ */

View file

@ -69,11 +69,11 @@ INCLUDES = -I. @INCLUDES@ -I$(top_srcdir)/lib/clixon -I$(top_srcdir)/include -I$
SRC = clixon_sig.c clixon_log.c clixon_err.c clixon_event.c \
clixon_string.c clixon_handle.c \
clixon_xml.c clixon_xml_sort.c clixon_xml_map.c clixon_file.c \
clixon_json.c clixon_yang.c clixon_yang_type.c \
clixon_json.c clixon_yang.c clixon_yang_type.c clixon_yang_module.c \
clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_client.c \
clixon_xsl.c clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \
clixon_xml_db.c clixon_netconf_lib.c
clixon_xpath.c clixon_xpath_ctx.c clixon_sha1.c \
clixon_xml_db.c clixon_netconf_lib.c clixon_stream.c
YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
lex.clixon_yang_parse.o clixon_yang_parse.tab.o \

View file

@ -75,6 +75,7 @@ struct event_data{
/*
* Internal variables
* XXX consider use handle variables instead of global
*/
static struct event_data *ee = NULL;
static struct event_data *ee_timers = NULL;
@ -86,7 +87,7 @@ static int _clicon_exit = 0;
/*! For signal handlers: instead of doing exit, set a global variable to exit
* Status is then checked in event_loop.
* Note it maybe would be better to do use on a handle basis, bit a signal
* Note it maybe would be better to do use on a handle basis, but a signal
* handler is global
*/
int
@ -96,6 +97,15 @@ clicon_exit_set(void)
return 0;
}
/*! Set exit to 0
*/
int
clicon_exit_reset(void)
{
_clicon_exit = 0;
return 0;
}
/*! Get the status of global exit variable, usually set by signal handlers
*/
int
@ -274,6 +284,8 @@ event_poll(int fd)
/*! Dispatch file descriptor events (and timeouts) by invoking callbacks.
* There is an issue with fairness that timeouts may take over all events
* One could try to poll the file descriptors after a timeout?
* @retval 0 OK
* @retval -1 Error: eg select, callback, timer,
*/
int
event_loop(void)

View file

@ -112,8 +112,7 @@ clicon_file_dirent(const char *dir,
char errbuf[128];
char filename[MAXPATHLEN];
struct stat st;
struct dirent dent;
struct dirent *dresp;
struct dirent *dent;
struct dirent *tmp;
struct dirent *new = NULL;
struct dirent *dvecp = NULL;
@ -132,22 +131,15 @@ clicon_file_dirent(const char *dir,
clicon_err(OE_UNIX, errno, "opendir(%s)", dir);
goto quit;
}
for (res = readdir_r(dirp, &dent, &dresp);
dresp;
res = readdir_r(dirp, &dent, &dresp)) {
if (res != 0) {
clicon_err(OE_UNIX, 0, "readdir: %s", strerror(errno));
goto quit;
}
while((dent = readdir(dirp)) != NULL) {
/* Filename matching */
if (regexp) {
if (regexec(&re, dent.d_name, (size_t) 0, NULL, 0) != 0)
if (regexec(&re, dent->d_name, (size_t) 0, NULL, 0) != 0)
continue;
}
/* File type matching */
if (type) {
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dent.d_name);
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dent->d_name);
res = lstat(filename, &st);
if (res != 0) {
clicon_err(OE_UNIX, 0, "lstat: %s", strerror(errno));
@ -161,7 +153,7 @@ clicon_file_dirent(const char *dir,
goto quit;
}
new = tmp;
memcpy(&new[nent], &dent, sizeof(dent));
memcpy(&new[nent], dent, sizeof(*dent));
nent++;
} /* while */

View file

@ -53,6 +53,8 @@
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_stream.h"
#include "clixon_options.h"
#define CLICON_MAGIC 0x99aafabe
@ -65,9 +67,10 @@
* @see struct backend_handle
*/
struct clicon_handle {
int ch_magic; /* magic (HDR) */
clicon_hash_t *ch_copt; /* clicon option list (HDR) */
clicon_hash_t *ch_data; /* internal clicon data (HDR) */
int ch_magic; /* magic (HDR) */
clicon_hash_t *ch_copt; /* clicon option list (HDR) */
clicon_hash_t *ch_data; /* internal clicon data (HDR) Unclear why two? */
event_stream_t *ch_stream; /* notification streams, see clixon_stream.[ch] */
};
/*! Internal call to allocate a CLICON handle.
@ -126,13 +129,13 @@ int
clicon_handle_exit(clicon_handle h)
{
struct clicon_handle *ch = handle(h);
clicon_hash_t *copt;
clicon_hash_t *data;
clicon_hash_t *ha;
if ((copt = clicon_options(h)) != NULL)
hash_free(copt);
if ((data = clicon_data(h)) != NULL)
hash_free(data);
if ((ha = clicon_options(h)) != NULL)
hash_free(ha);
if ((ha = clicon_data(h)) != NULL)
hash_free(ha);
stream_delete_all(h);
free(ch);
return 0;
}
@ -172,3 +175,34 @@ clicon_data(clicon_handle h)
return ch->ch_data;
}
/*! Return stream hash-array given a clicon handle.
* @param[in] h Clicon handle
*/
event_stream_t *
clicon_stream(clicon_handle h)
{
struct clicon_handle *ch = handle(h);
return ch->ch_stream;
}
int
clicon_stream_set(clicon_handle h,
event_stream_t *es)
{
struct clicon_handle *ch = handle(h);
ch->ch_stream = es;
return 0;
}
int
clicon_stream_append(clicon_handle h,
event_stream_t *es)
{
struct clicon_handle *ch = handle(h);
ADDQ(es, ch->ch_stream);
return 0;
}

View file

@ -52,6 +52,9 @@
#include <sys/time.h>
#include <sys/types.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
@ -62,12 +65,8 @@ int debug = 0;
/* Bitmask whether to log to syslog or stderr: CLICON_LOG_STDERR | CLICON_LOG_SYSLOG */
static int _logflags = 0x0;
/* Function pointer to log notify callback */
static clicon_log_notify_t *_log_notify_cb = NULL;
static void *_log_notify_arg = NULL;
/* Set to open file to bypass logging and write debug messages directly to file */
static FILE *_debugfile = NULL;
/* Set to open file to write debug messages directly to file */
static FILE *_logfile = NULL;
/*! Initialize system logger.
*
@ -81,19 +80,75 @@ static FILE *_debugfile = NULL;
* if CLICON_LOG_SYSLOG, then print logs to syslog
* You can do a combination of both
* @code
* clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
* clicon_log_init(h, __PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
* @endcode
*/
int
clicon_log_init(char *ident,
int upto,
int flags)
clicon_log_init(char *ident,
int upto,
int flags)
{
if (setlogmask(LOG_UPTO(upto)) < 0)
/* Cant syslog here */
fprintf(stderr, "%s: setlogmask: %s\n", __FUNCTION__, strerror(errno));
_logflags = flags;
openlog(ident, LOG_PID, LOG_USER); /* LOG_PUSER is achieved by direct stderr logs in clicon_log */
if (flags & CLICON_LOG_SYSLOG){
if (setlogmask(LOG_UPTO(upto)) < 0)
/* Cant syslog here */
fprintf(stderr, "%s: setlogmask: %s\n", __FUNCTION__, strerror(errno));
openlog(ident, LOG_PID, LOG_USER); /* LOG_PUSER is achieved by direct stderr logs in clicon_log */
}
return 0;
}
int
clicon_log_exit(void)
{
if (_logfile)
fclose(_logfile);
return 0;
}
/*! Utility function to set log destination/flag using command-line option
* @param[in] c Log option,one of s,f,e,o
* @retval -1 No match
* @retval 0 One of CLICON_LOG_SYSLOG|STDERR|STDOUT|FILE
*/
int
clicon_log_opt(char c)
{
int logdst = -1;
switch (c){
case 's':
logdst = CLICON_LOG_SYSLOG;
break;
case 'e':
logdst = CLICON_LOG_STDERR;
break;
case 'o':
logdst = CLICON_LOG_STDOUT;
break;
case 'f':
logdst = CLICON_LOG_FILE;
break;
default:
break;
}
return logdst;
}
/*! If log flags include CLICON_LOG_FILE, set the file
* @param[in] filename File to log to
* @retval 0 OK
* @retval -1 Error
*/
int
clicon_log_file(char *filename)
{
if (_logfile)
fclose(_logfile);
if ((_logfile = fopen(filename, "a")) == NULL){
fprintf(stderr, "fopen: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
return -1;
}
return 0;
}
@ -103,18 +158,6 @@ clicon_get_logflags(void)
return _logflags;
}
/*! Register log callback, return old setting
*/
clicon_log_notify_t *
clicon_log_register_callback(clicon_log_notify_t *cb,
void *arg)
{
clicon_log_notify_t *old = _log_notify_cb;
_log_notify_cb = cb;
_log_notify_arg = arg;
return old;
}
/*! Mimic syslog and print a time on file f
*/
static int
@ -131,6 +174,7 @@ flogtime(FILE *f)
return 0;
}
#ifdef NOTUSED
/*
* Mimic syslog and print a time on string s
* String returned needs to be freed.
@ -154,19 +198,19 @@ slogtime(void)
tm->tm_hour, tm->tm_min, tm->tm_sec);
return str;
}
#endif
/*! Make a logging call to syslog (or stderr).
*
* @param[in] level log level, eg LOG_DEBUG,LOG_INFO,...,LOG_EMERG. Thisis OR:d with facility == LOG_USER
* @param[in] msg Message to print as argv.
* This is the _only_ place the actual syslog (or stderr) logging is made in clicon,..
* @note syslog makes itw own filtering, but if log to stderr we do it here
* @note syslog makes its own filtering, but if log to stderr we do it here
* @see clicon_debug
*/
int
clicon_log_str(int level,
char *msg)
static int
clicon_log_str(int level,
char *msg)
{
if (_logflags & CLICON_LOG_SYSLOG)
syslog(LOG_MAKEPRI(LOG_USER, level), "%s", msg);
@ -183,30 +227,15 @@ clicon_log_str(int level,
flogtime(stdout);
fprintf(stdout, "%s\n", msg);
}
if (_log_notify_cb){
static int cb = 0;
char *d, *msg2;
int len;
if (cb++ == 0){
/* Here there is danger of recursion: if callback in turn logs, therefore
make static check (should be stack-based - now global)
*/
if ((d = slogtime()) == NULL)
return -1;
len = strlen(d) + strlen(msg) + 1;
if ((msg2 = malloc(len)) == NULL){
fprintf(stderr, "%s: malloc: %s\n", __FUNCTION__, strerror(errno));
return -1;
}
snprintf(msg2, len, "%s%s", d, msg);
assert(_log_notify_arg);
_log_notify_cb(level, msg2, _log_notify_arg);
free(d);
free(msg2);
}
cb--;
if ((_logflags & CLICON_LOG_FILE) && _logfile){
flogtime(_logfile);
fprintf(_logfile, "%s\n", msg);
fflush(_logfile);
}
/* Enable this if you want syslog in a stream. But there are problems with
* recursion
*/
done:
return 0;
}
@ -326,13 +355,7 @@ clicon_debug(int dbglevel,
goto done;
}
va_end(args);
if (_debugfile != NULL){ /* Bypass syslog altogether */
/* XXX: Here use date sub-routine as found in err_print1 */
flogtime(_debugfile);
fprintf(_debugfile, "%s\n", msg);
}
else
clicon_log_str(LOG_DEBUG, msg);
clicon_log_str(LOG_DEBUG, msg);
retval = 0;
done:
if (msg)

View file

@ -60,6 +60,8 @@
#include "clixon_yang.h"
#include "clixon_log.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_xml_map.h"
#include "clixon_netconf_lib.h"
/*! Create Netconf in-use error XML tree according to RFC 6241 Appendix A
@ -84,7 +86,7 @@ netconf_in_use(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -123,7 +125,7 @@ netconf_invalid_value(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -163,7 +165,7 @@ netconf_too_big(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -205,7 +207,7 @@ netconf_missing_attribute(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -246,7 +248,7 @@ netconf_bad_attribute(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -288,7 +290,7 @@ netconf_unknown_attribute(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -330,7 +332,7 @@ netconf_missing_element(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -373,7 +375,7 @@ netconf_bad_element(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -415,7 +417,7 @@ netconf_unknown_element(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -457,7 +459,7 @@ netconf_unknown_namespace(cbuf *cb,
type, info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -496,7 +498,7 @@ netconf_access_denied(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -572,7 +574,7 @@ netconf_lock_denied(cbuf *cb,
info) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -611,7 +613,7 @@ netconf_resource_denied(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -651,7 +653,7 @@ netconf_rollback_failed(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -689,7 +691,7 @@ netconf_data_exists(cbuf *cb,
"<error-severity>error</error-severity>") <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -727,7 +729,7 @@ netconf_data_missing(cbuf *cb,
"<error-severity>error</error-severity>") <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -767,7 +769,7 @@ netconf_operation_not_supported(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -807,7 +809,7 @@ netconf_operation_failed(cbuf *cb,
type) <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -882,7 +884,7 @@ netconf_malformed_message(cbuf *cb,
"<error-severity>error</error-severity>") <0)
goto err;
if (message){
if (xml_chardata_encode(message, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", message) < 0)
goto done;
if (cprintf(cb, "<error-message>%s</error-message>", encstr) < 0)
goto err;
@ -934,3 +936,76 @@ netconf_malformed_message_xml(cxobj **xret,
done:
return retval;
}
/*! Help function: merge - check yang - if error make netconf errmsg
* @param[in] x XML tree
* @param[in] yspec Yang spec
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
* @retval 0 OK
* @retval 1 Statedata callback failed
*/
int
netconf_trymerge(cxobj *x,
yang_spec *yspec,
cxobj **xret)
{
int retval = -1;
char *reason = NULL;
cxobj *xc;
if (xml_merge(*xret, x, yspec, &reason) < 0)
goto done;
if (reason){
while ((xc = xml_child_i(*xret, 0)) != NULL)
xml_purge(xc);
if (netconf_operation_failed_xml(xret, "rpc", reason)< 0)
goto done;
retval = 1;
goto done;
}
retval = 0;
done:
if (reason)
free(reason);
return retval;
}
/*! Load ietf netconf yang module and set enabled features
* The features added are:
* candidate (8.3)
* validate (8.6)
* startup (8.7)
* xpath (8.9)
*/
int
netconf_module_load(clicon_handle h)
{
int retval = -1;
cxobj *xc;
// cxobj *x;
yang_spec *yspec;
yspec = clicon_dbspec_yang(h);
/* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
if (yang_spec_parse_module(h, "ietf-netconf-notification", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
if ((xc = clicon_conf_xml(h)) == NULL){
clicon_err(OE_CFG, ENOENT, "Clicon configuration not loaded");
goto done;
}
/* Enable features (hardcoded here) */
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:candidate</CLICON_FEATURE>", yspec, &xc) < 0)
goto done;
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:validate</CLICON_FEATURE>", yspec, &xc) < 0)
goto done;
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>", yspec, &xc) < 0)
goto done;
if (xml_parse_string("<CLICON_FEATURE>ietf-netconf:xpath</CLICON_FEATURE>", yspec, &xc) < 0)
goto done;
retval = 0;
done:
return retval;
}

View file

@ -64,10 +64,9 @@
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_plugin.h"
#include "clixon_xsl.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xml_map.h"
@ -117,12 +116,16 @@ clicon_option_dump(clicon_handle h,
}
/*! Read filename and set values to global options registry. XML variant.
* @see clicon_option_readfile
*
* @param[out] xconfig Pointer to xml config tree. Should be freed by caller
* @retval 0 OK
* @retval -1 Error
*/
static int
clicon_option_readfile_xml(clicon_hash_t *copt,
const char *filename,
yang_spec *yspec)
static int
parse_configfile(clicon_handle h,
const char *filename,
yang_spec *yspec,
cxobj **xconfig)
{
struct stat st;
FILE *f = NULL;
@ -133,6 +136,7 @@ clicon_option_readfile_xml(clicon_hash_t *copt,
cxobj *x = NULL;
char *name;
char *body;
clicon_hash_t *copt = clicon_options(h);
if (filename == NULL || !strlen(filename)){
clicon_err(OE_UNIX, 0, "Not specified");
@ -169,14 +173,22 @@ clicon_option_readfile_xml(clicon_hash_t *copt,
while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) {
name = xml_name(x);
body = xml_body(x);
if (name && body &&
(hash_add(copt,
name,
body,
strlen(body)+1)) == NULL)
if (name==NULL || body == NULL){
clicon_log(LOG_WARNING, "%s option NULL: name:%s body:%s",
__FUNCTION__, name, body);
continue;
}
if (strcmp(name,"CLICON_FEATURE")==0)
continue;
if (hash_add(copt,
name,
body,
strlen(body)+1) == NULL)
goto done;
}
retval = 0;
*xconfig = xt;
xt = NULL;
done:
if (xt)
xml_free(xt);
@ -185,21 +197,27 @@ clicon_option_readfile_xml(clicon_hash_t *copt,
return retval;
}
/*! Initialize option values
/*! Parse clixon yang file. Parse XML config file. Initialize option values
*
* Set default options, Read config-file, Check that all values are set.
* Parse clixon yang file and save in yspec.
* Read clixon system config files
* @param[in] h clicon handle
* @param[in] h clicon handle
* @param[in] yspec Yang spec of clixon config file
* @note Due to Bug: Top-level Yang symbol cannot be called "config" in any
* imported yang file, the config module needs to be isolated from all
* other yang modules.
*/
int
clicon_options_main(clicon_handle h)
clicon_options_main(clicon_handle h,
yang_spec *yspec)
{
int retval = -1;
char *configfile;
clicon_hash_t *copt = clicon_options(h);
char *suffix;
char xml = 0; /* Configfile is xml, otherwise legacy */
yang_spec *yspec = NULL;
cxobj *xconfig = NULL;
/*
* Set configure file if not set by command-line above
@ -214,36 +232,34 @@ clicon_options_main(clicon_handle h)
suffix++;
xml = strcmp(suffix, "xml") == 0;
}
if (xml){ /* Read clixon yang file */
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_parse(h, CLIXON_DATADIR, "clixon-config", NULL, yspec) < 0)
goto done;
/* Read configfile */
if (clicon_option_readfile_xml(copt, configfile, yspec) < 0)
goto done;
/* Specific option handling */
if (clicon_option_bool(h, "CLICON_XML_SORT") == 1)
xml_child_sort = 1;
else
xml_child_sort = 0;
}
else {
if (xml == 0){
clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix);
goto done;
}
/* Parse clixon yang spec */
if (yang_parse(h, NULL, "clixon-config", CLIXON_DATADIR, NULL, yspec, NULL) < 0)
goto done;
/* Read configfile */
if (parse_configfile(h, configfile, yspec, &xconfig) < 0)
goto done;
if (xml_rootchild(xconfig, 0, &xconfig) < 0)
goto done;
clicon_conf_xml_set(h, xconfig);
/* Specific option handling */
if (clicon_option_bool(h, "CLICON_XML_SORT") == 1)
xml_child_sort = 1;
else
xml_child_sort = 0;
retval = 0;
done:
if (yspec) /* The clixon yang-spec is not used after this */
yspec_free(yspec);
return retval;
}
/*! Check if a clicon option has a value
* @param[in] h clicon_handle
* @param[in] name option name
* @retval
* @param[in] h clicon_handle
* @param[in] name option name
* @retval !=0 option exists
* @retval 0 option does not exists
*/
int
clicon_option_exists(clicon_handle h,
@ -572,26 +588,27 @@ clicon_dbspec_yang_set(clicon_handle h,
return 0;
}
/*! Get YANG NETCONF specification
#if 1 /* Temporary function until "Top-level Yang symbol cannot be called "config"" is fixed */
/*! Get YANG specification for clixon config
* Must use hash functions directly since they are not strings.
*/
yang_spec *
clicon_netconf_yang(clicon_handle h)
clicon_config_yang(clicon_handle h)
{
clicon_hash_t *cdat = clicon_data(h);
size_t len;
void *p;
if ((p = hash_value(cdat, "netconf_yang", &len)) != NULL)
if ((p = hash_value(cdat, "control_yang", &len)) != NULL)
return *(yang_spec **)p;
return NULL;
}
/*! Set yang netconf specification
/*! Set yang specification for control
* ys must be a malloced pointer
*/
int
clicon_netconf_yang_set(clicon_handle h,
clicon_config_yang_set(clicon_handle h,
struct yang_spec *ys)
{
clicon_hash_t *cdat = clicon_data(h);
@ -599,29 +616,43 @@ clicon_netconf_yang_set(clicon_handle h,
/* It is the pointer to ys that should be copied by hash,
so we send a ptr to the ptr to indicate what to copy.
*/
if (hash_add(cdat, "netconf_yang", &ys, sizeof(ys)) == NULL)
if (hash_add(cdat, "control_yang", &ys, sizeof(ys)) == NULL)
return -1;
return 0;
}
#endif
/*! Get dbspec name as read from spec. Can be used in CLI '@' syntax.
* XXX: this we muśt change,...
/*! Get YANG specification for Clixon system options and features
* Must use hash functions directly since they are not strings.
* Example: features are typically accessed directly in the config tree.
*/
char *
clicon_dbspec_name(clicon_handle h)
cxobj *
clicon_conf_xml(clicon_handle h)
{
if (!clicon_option_exists(h, "dbspec_name"))
return NULL;
return clicon_option_str(h, "dbspec_name");
clicon_hash_t *cdat = clicon_data(h);
size_t len;
void *p;
if ((p = hash_value(cdat, "clixon_conf", &len)) != NULL)
return *(cxobj **)p;
return NULL;
}
/*! Set dbspec name as read from spec. Can be used in CLI '@' syntax.
/*! Set YANG specification for Clixon system options and features
* ys must be a malloced pointer
*/
int
clicon_dbspec_name_set(clicon_handle h, char *name)
clicon_conf_xml_set(clicon_handle h,
cxobj *x)
{
return clicon_option_str_set(h, "dbspec_name", name);
clicon_hash_t *cdat = clicon_data(h);
/* It is the pointer to x that should be copied by hash,
* so we send a ptr to the ptr to indicate what to copy.
*/
if (hash_add(cdat, "clixon_conf", &x, sizeof(x)) == NULL)
return -1;
return 0;
}
/*! Get xmldb datastore plugin handle, as used by dlopen/dlsym/dlclose */
@ -717,7 +748,6 @@ clicon_xmldb_handle_set(clicon_handle h,
return 0;
}
/*! Get authorized user name
* @param[in] h Clicon handle
* @retval xh XMLDB storage handle. If not connected return NULL

View file

@ -130,8 +130,10 @@ clixon_plugin_each_revert(clicon_handle h,
clixon_plugin *cp = NULL;
clixon_plugin *cpnext = NULL;
if (cpprev == NULL)
cpnext = &_clixon_plugins[nr-1];
if (cpprev == NULL){
if (nr>0)
cpnext = &_clixon_plugins[nr-1];
}
else{
for (i = nr-1; i >= 0; i--) {
cp = &_clixon_plugins[i];
@ -189,7 +191,7 @@ plugin_load_one(clicon_handle h,
char *name;
char *p;
clicon_debug(1, "%s", __FUNCTION__);
clicon_debug(1, "%s file:%s function:%s", __FUNCTION__, file, function);
dlerror(); /* Clear any existing error */
if ((handle = dlopen(file, dlflags)) == NULL) {
error = (char*)dlerror();
@ -320,7 +322,7 @@ clixon_plugin_start(clicon_handle h,
continue;
// optind = 0;
if (startfn(h, argc, argv) < 0) {
clicon_debug(1, "plugin_start() failed\n");
clicon_debug(1, "plugin_start() failed");
return -1;
}
}
@ -343,7 +345,7 @@ clixon_plugin_exit(clicon_handle h)
if ((exitfn = cp->cp_api.ca_exit) == NULL)
continue;
if (exitfn(h) < 0) {
clicon_debug(1, "plugin_exit() failed\n");
clicon_debug(1, "plugin_exit() failed");
return -1;
}
if (dlclose(cp->cp_handle) != 0) {
@ -385,7 +387,7 @@ clixon_plugin_auth(clicon_handle h,
if ((authfn = cp->cp_api.ca_auth) == NULL)
continue;
if ((retval = authfn(h, arg)) < 0) {
clicon_debug(1, "plugin_auth() failed\n");
clicon_debug(1, "plugin_auth() failed");
return -1;
}
break;

View file

@ -64,14 +64,13 @@
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_sig.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_proto.h"
static int _atomicio_sig = 0;
@ -246,9 +245,10 @@ atomicio(ssize_t (*fn) (int, void *, size_t),
if (!_atomicio_sig)
continue;
}
else
if (errno == EAGAIN)
continue;
else if (errno == EAGAIN)
continue;
else if (errno == ECONNRESET)/* Connection reset by peer */
res = 0;
case 0: /* fall thru */
return (res);
default:
@ -564,16 +564,16 @@ send_msg_reply(int s,
* @param[in] event
* @retval 0 OK
* @retval -1 Error
* @see send_msg_notify_xml
*/
int
static int
send_msg_notify(int s,
int level,
char *event)
{
int retval = -1;
struct clicon_msg *msg = NULL;
if ((msg=clicon_msg_encode("<notification><event>%s</event></notification>", event)) == NULL)
if ((msg=clicon_msg_encode("%s", event)) == NULL)
goto done;
if (clicon_msg_send(s, msg) < 0)
goto done;
@ -584,6 +584,37 @@ send_msg_notify(int s,
return retval;
}
/*! Send a clicon_msg NOTIFY message asynchronously to client
*
* @param[in] s Socket to communicate with client
* @param[in] level
* @param[in] xml Event as XML
* @retval 0 OK
* @retval -1 Error
* @see send_msg_notify XXX beauty contest
*/
int
send_msg_notify_xml(int s,
cxobj *xev)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xev, 0, 0) < 0)
goto done;
if (send_msg_notify(s, cbuf_get(cb)) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Look for a text pattern in an input string, one char at a time
* @param[in] tag What to look for
* @param[in] ch New input character

View file

@ -57,14 +57,13 @@
/* clicon */
#include "clixon_queue.h"
#include "clixon_log.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_plugin.h"
#include "clixon_xsl.h"
#include "clixon_string.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
@ -152,8 +151,14 @@ clicon_rpc_msg(clicon_handle h,
* Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle
* @param[in] xmlstr XML netconf tree as string
* @param[out] xret Return XML netconf tree, error or OK
* @param[out] xret Return XML netconf tree, error or OK (need to be freed)
* @param[out] sp Socket pointer for notification, otherwise NULL
* @code
* cxobj *xret = NULL;
* if (clicon_rpc_netconf(h, "<rpc></rpc>", &xret, NULL) < 0)
* err;
* xml_free(xret);
* @endcode
* @see clicon_rpc_netconf_xml xml as tree instead of string
*/
int
@ -182,6 +187,14 @@ clicon_rpc_netconf(clicon_handle h,
* @param[in] xml XML netconf tree
* @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL
* @code
* cxobj *xret = NULL;
* int s;
* if (clicon_rpc_netconf_xml(h, x, &xret, &s) < 0)
* err;
* xml_free(xret);
* @endcode
* @see clicon_rpc_netconf xml as string instead of tree
*/
int
@ -776,7 +789,7 @@ clicon_rpc_create_subscription(clicon_handle h,
username = clicon_username_get(h);
if ((msg = clicon_msg_encode("<rpc username=\"%s\"><create-subscription>"
"<stream>%s</stream>"
"<filter>%s</filter>"
"<filter type=\"xpath\" select=\"%s\" />"
"</create-subscription></rpc>",
username?username:"",
stream?stream:"", filter?filter:"")) == NULL)

1049
lib/src/clixon_stream.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -174,49 +174,77 @@ uri_unreserved(unsigned char in)
}
/*! Percent encoding according to RFC 3986 URI Syntax
* @param[in] str Not-encoded input string
* @param[out] escp Encoded/escaped malloced output string
* @param[out] encp Encoded malloced output string
* @param[in] fmt Not-encoded input string (stdarg format string)
* @param[in] ... stdarg variable parameters
* @retval 0 OK
* @retval -1 Error
* @code
* char *enc;
* if (uri_percent_encode(&enc, "formatstr: <>= %s", "substr<>") < 0)
* err;
* if(enc)
* free(enc);
* @endcode
* @see RFC 3986 Uniform Resource Identifier (URI): Generic Syntax
* @see uri_percent_decode
* @see xml_chardata_encode
*/
int
uri_percent_encode(char *str,
char **escp)
uri_percent_encode(char **encp,
char *fmt, ...)
{
int retval = -1;
char *esc = NULL;
int len;
int i, j;
int retval = -1;
char *str = NULL; /* Expanded format string w stdarg */
char *enc = NULL;
int fmtlen;
int len;
int i, j;
va_list args;
/* Two steps: (1) read in the complete format string */
va_start(args, fmt); /* dryrun */
fmtlen = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
if ((str = malloc(fmtlen)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, fmtlen);
va_start(args, fmt); /* real */
fmtlen = vsnprintf(str, fmtlen, fmt, args) + 1;
va_end(args);
/* Now str is the combined fmt + ... */
/* Step (2) encode and expand str --> enc */
/* This is max */
len = strlen(str)*3+1;
if ((esc = malloc(len)) == NULL){
if ((enc = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(esc, 0, len);
memset(enc, 0, len);
j = 0;
for (i=0; i<strlen(str); i++){
if (uri_unreserved(str[i]))
esc[j++] = str[i];
enc[j++] = str[i];
else{
snprintf(&esc[j], 4, "%%%02X", str[i]&0xff);
snprintf(&enc[j], 4, "%%%02X", str[i]&0xff);
j += 3;
}
}
*escp = esc;
*encp = enc;
retval = 0;
done:
if (retval < 0 && esc)
free(esc);
if (str)
free(str);
if (retval < 0 && enc)
free(enc);
return retval;
}
/*! Percent decoding according to RFC 3986 URI Syntax
* @param[in] esc Escaped/encoded input string
* @param[in] enc Encoded input string
* @param[out] strp Decoded malloced output string. Deallocate with free()
* @retval 0 OK
* @retval -1 Error
@ -224,7 +252,7 @@ uri_percent_encode(char *str,
* @see uri_percent_encode
*/
int
uri_percent_decode(char *esc,
uri_percent_decode(char *enc,
char **strp)
{
int retval = -1;
@ -235,24 +263,24 @@ uri_percent_decode(char *esc,
char *ptr;
/* This is max */
len = strlen(esc)+1;
len = strlen(enc)+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];
for (i=0; i<strlen(enc); i++){
if (enc[i] == '%' && strlen(enc)-i > 2 &&
isxdigit(enc[i+1]) && isxdigit(enc[i+2])){
hstr[0] = enc[i+1];
hstr[1] = enc[i+2];
hstr[2] = 0;
str[j] = strtoul(hstr, &ptr, 16);
i += 2;
}
else
str[j] = esc[i];
str[j] = enc[i];
j++;
}
str[j++] = '\0';
@ -265,8 +293,9 @@ uri_percent_decode(char *esc,
}
/*! Escape characters according to XML definition
* @param[in] str Not-encoded input string
* @param[out] escp Encoded/escaped malloced output string
* @param[out] encp Encoded malloced output string
* @param[in] fmt Not-encoded input string (stdarg format string)
* @param[in] ... stdarg variable parameters
* @retval 0 OK
* @retval -1 Error
* @see https://www.w3.org/TR/2008/REC-xml-20081126/#syntax chapter 2.6
@ -274,8 +303,7 @@ uri_percent_decode(char *esc,
* @see AMPERSAND mode in clixon_xml_parse.l
* @code
* char *encstr = NULL;
* char *val = "a<>b";
* if (xml_chardata_encode(str, &encstr) < 0)
* if (xml_chardata_encode(&encstr, "fmtstr<>& %s", "substr<>") < 0)
* err;
* if (encstr)
* free(encstr);
@ -289,16 +317,34 @@ uri_percent_decode(char *esc,
* Optionally >
*/
int
xml_chardata_encode(char *str,
char **escp)
xml_chardata_encode(char **escp,
char *fmt,...)
{
int retval = -1;
char *esc = NULL;
int l;
int len;
int i, j;
int cdata; /* when set, skip encoding */
int retval = -1;
char *str = NULL; /* Expanded format string w stdarg */
int fmtlen;
char *esc = NULL;
int l;
int len;
int i, j;
int cdata; /* when set, skip encoding */
va_list args;
/* Two steps: (1) read in the complete format string */
va_start(args, fmt); /* dryrun */
fmtlen = vsnprintf(NULL, 0, fmt, args) + 1;
va_end(args);
if ((str = malloc(fmtlen)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, fmtlen);
va_start(args, fmt); /* real */
fmtlen = vsnprintf(str, fmtlen, fmt, args) + 1;
va_end(args);
/* Now str is the combined fmt + ... */
/* Step (2) encode and expand str --> enc */
/* First compute length (do nothing) */
len = 0; cdata = 0;
for (i=0; i<strlen(str); i++){
@ -310,7 +356,7 @@ xml_chardata_encode(char *str,
else
switch (str[i]){
case '&':
len += strlen("&amp; ");
len += strlen("&amp;");
break;
case '<':
if (strncmp(&str[i], "<![CDATA[", strlen("<![CDATA[")) == 0){
@ -318,10 +364,10 @@ xml_chardata_encode(char *str,
cdata++;
}
else
len += strlen("&lt; ");
len += strlen("&lt;");
break;
case '>':
len += strlen("&gt; ");
len += strlen("&gt;");
break;
default:
len++;
@ -349,7 +395,7 @@ xml_chardata_encode(char *str,
else
switch (str[i]){
case '&':
if ((l=snprintf(&esc[j], 7, "&amp; ")) < 0){
if ((l=snprintf(&esc[j], 6, "&amp;")) < 0){
clicon_err(OE_UNIX, errno, "snprintf");
goto done;
}
@ -361,14 +407,14 @@ xml_chardata_encode(char *str,
cdata++;
break;
}
if ((l=snprintf(&esc[j], 6, "&lt; ")) < 0){
if ((l=snprintf(&esc[j], 5, "&lt;")) < 0){
clicon_err(OE_UNIX, errno, "snprintf");
goto done;
}
j += l;
break;
case '>':
if ((l=snprintf(&esc[j], 6, "&gt; ")) < 0){
if ((l=snprintf(&esc[j], 5, "&gt;")) < 0){
clicon_err(OE_UNIX, errno, "snprintf");
goto done;
}
@ -381,6 +427,8 @@ xml_chardata_encode(char *str,
*escp = esc;
retval = 0;
done:
if (str)
free(str);
if (retval < 0 && esc)
free(esc);
return retval;

View file

@ -55,11 +55,11 @@
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_log.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_sort.h"
@ -1018,7 +1018,7 @@ clicon_xml2file(FILE *f,
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
if (xml_chardata_encode(val, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", val) < 0)
goto done;
fprintf(f, "%s", encstr);
break;
@ -1142,7 +1142,7 @@ clicon_xml2cbuf(cbuf *cb,
case CX_BODY:
if ((val = xml_value(x)) == NULL) /* incomplete tree */
break;
if (xml_chardata_encode(val, &encstr) < 0)
if (xml_chardata_encode(&encstr, "%s", val) < 0)
goto done;
cprintf(cb, "%s", encstr);
break;
@ -1412,7 +1412,8 @@ xml_parse_file(int fd,
* cxobj *xt = NULL;
* if (xml_parse_string(str, yspec, &xt) < 0)
* err;
* xml_free(xt);
* if (xml_root_child(xt, 0, &xt) < 0) # If you want to remove TOP
* err;
* @endcode
* @see xml_parse_file
* @see xml_parse_va

View file

@ -143,10 +143,8 @@ xmldb_plugin_unload(clicon_handle h)
xmldb_handle xh;
char *error;
if ((handle = clicon_xmldb_plugin_get(h)) == NULL){
clicon_err(OE_PLUGIN, errno, "No plugin handle");
goto done;
}
if ((handle = clicon_xmldb_plugin_get(h)) == NULL)
goto ok; /* OK, may not have been initialized */
/* If connected storage handle then disconnect */
if ((xh = clicon_xmldb_handle_get(h)) != NULL)
xmldb_disconnect(h); /* sets xmldb handle to NULL */
@ -165,8 +163,9 @@ xmldb_plugin_unload(clicon_handle h)
clicon_err(OE_PLUGIN, errno, "dlclose: %s", error ? error : "Unknown error");
/* Just report no -1 return*/
}
ok:
retval = 0;
done:
// done:
return retval;
}
@ -231,6 +230,7 @@ xmldb_disconnect(clicon_handle h)
xmldb_handle xh;
struct xmldb_api *xa;
clicon_debug(1, "%s", __FUNCTION__);
if ((xa = clicon_xmldb_api_get(h)) == NULL){
clicon_err(OE_DB, 0, "No xmldb plugin");
goto done;

View file

@ -80,12 +80,11 @@
#include "clixon_string.h"
#include "clixon_yang.h"
#include "clixon_yang_type.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_plugin.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
#include "clixon_xsl.h"
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_xml_sort.h"
@ -1007,7 +1006,7 @@ api_path_fmt2api_path(char *api_path_fmt,
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (uri_percent_encode(str, &strenc) < 0)
if (uri_percent_encode(&strenc, "%s", str) < 0)
goto done;
cprintf(cb, "%s", strenc);
free(strenc); strenc = NULL;
@ -1088,13 +1087,7 @@ api_path_fmt2xpath(char *api_path_fmt,
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
cprintf(cb,
#ifdef COMPAT_XSL
"[%s=%s]",
#else
"[%s='%s']",
#endif
cv_name_get(cv), str);
cprintf(cb, "[%s='%s']", cv_name_get(cv), str);
free(str);
}
}
@ -1511,13 +1504,7 @@ api_path2xpath_cvv(yang_spec *yspec,
cprintf(xpath, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(xpath,
#ifdef COMPAT_XSL
"[%s=%s]",
#else
"[%s='%s']",
#endif
cv_string_get(cvi), v);
cprintf(xpath, "[%s='%s']", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)

View file

@ -89,17 +89,18 @@ int clixon_xml_parsewrap(void)
return NAME; /* rather be catch-all */
}
<START>[ \t]+ ;
<START>\: return *clixon_xml_parsetext;
<START>\: return *clixon_xml_parsetext;
<START>\n { _YA->ya_linenum++;}
<START>"<?xml" { BEGIN(TEXTDECL); return BTEXT;}
<START>"/>" { BEGIN(STATEA); return ESLASH; }
<START>"<!--" { BEGIN(CMNT); return BCOMMENT; }
<START>"</" return BSLASH;
<START>[/=] return *clixon_xml_parsetext;
<START>\< return *clixon_xml_parsetext;
<START>"</" return BSLASH;
<START>[/=] return *clixon_xml_parsetext;
<START>\< return *clixon_xml_parsetext;
<START>\> { BEGIN(STATEA); return *clixon_xml_parsetext; }
<START>\" { BEGIN(STR); return *clixon_xml_parsetext; }
<START>\" { _YA->ya_lex_state=START;BEGIN(STRDQ); return *clixon_xml_parsetext; }
<START>\' { _YA->ya_lex_state=START;BEGIN(STRSQ); return *clixon_xml_parsetext; }
<START>. { clixon_xml_parselval.string = yytext; return CHARDATA; /*XXX:optimize*/ }
<STATEA>"</" { BEGIN(START); return BSLASH; }
@ -112,36 +113,33 @@ int clixon_xml_parsewrap(void)
<STATEA>. { clixon_xml_parselval.string = yytext; return CHARDATA; /*XXX:optimize*/}
/* @see xml_chardata_encode */
<AMPERSAND>"amp; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "&"; return CHARDATA;}
<AMPERSAND>"lt; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "<"; return CHARDATA;}
<AMPERSAND>"gt; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = ">"; return CHARDATA;}
<AMPERSAND>"apos; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "'"; return CHARDATA;}
<AMPERSAND>"quot; " {BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "\""; return CHARDATA;}
<AMPERSAND>"amp;" { BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "&"; return CHARDATA;}
<AMPERSAND>"lt;" { BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "<"; return CHARDATA;}
<AMPERSAND>"gt;" { BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = ">"; return CHARDATA;}
<AMPERSAND>"apos;" { BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "'"; return CHARDATA;}
<AMPERSAND>"quot;" { BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = "\""; return CHARDATA;}
<CDATA>. { clixon_xml_parselval.string = yytext; return CHARDATA;}
<CDATA>\n { clixon_xml_parselval.string = yytext;_YA->ya_linenum++; return (CHARDATA);}
<CDATA>\n { clixon_xml_parselval.string = yytext;_YA->ya_linenum++; return (CHARDATA);}
<CDATA>"]]>" { BEGIN(_YA->ya_lex_state); clixon_xml_parselval.string = yytext; return CHARDATA;}
<CMNT>"-->" { BEGIN(START); return ECOMMENT; }
<CMNT>\n _YA->ya_linenum++;
<CMNT>\n _YA->ya_linenum++;
<CMNT>.
<TEXTDECL>encoding return ENC;
<TEXTDECL>version return VER;
<TEXTDECL>"=" return *clixon_xml_parsetext;
<TEXTDECL>"?>" { BEGIN(START);return ETEXT;}
<TEXTDECL>\" { BEGIN(STRDQ); return *clixon_xml_parsetext; }
<TEXTDECL>\' { BEGIN(STRSQ); return *clixon_xml_parsetext; }
<TEXTDECL>encoding return ENC;
<TEXTDECL>version return VER;
<TEXTDECL>"=" return *clixon_xml_parsetext;
<TEXTDECL>"?>" { BEGIN(START);return ETEXT;}
<TEXTDECL>\" { _YA->ya_lex_state =TEXTDECL;BEGIN(STRDQ); return *clixon_xml_parsetext; }
<TEXTDECL>\' { _YA->ya_lex_state =TEXTDECL;BEGIN(STRSQ); return *clixon_xml_parsetext; }
<STR>[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STR>\" { BEGIN(START); return *clixon_xml_parsetext; }
<STRDQ>1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRDQ>[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRDQ>\" { BEGIN(_YA->ya_lex_state); return *clixon_xml_parsetext; }
<STRDQ>1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRDQ>[^\"]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRDQ>\" { BEGIN(TEXTDECL); return *clixon_xml_parsetext; }
<STRSQ>1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRSQ>[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRSQ>\' { BEGIN(TEXTDECL); return *clixon_xml_parsetext; }
<STRSQ>1\.[0-9]+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRSQ>[^\']+ { clixon_xml_parselval.string = strdup(yytext); return CHARDATA; }
<STRSQ>\' { BEGIN(_YA->ya_lex_state); return *clixon_xml_parsetext; }
%%

View file

@ -401,6 +401,8 @@ attr : NAME '=' attvalue { if (xml_parse_attr(_YA, NULL, $1, $3)
attvalue : '\"' CHARDATA '\"' { $$=$2; /* $2 must be consumed */}
| '\"' '\"' { $$=strdup(""); /* $2 must be consumed */}
| '\'' CHARDATA '\'' { $$=$2; /* $2 must be consumed */}
| '\'' '\'' { $$=strdup(""); /* $2 must be consumed */}
;
%%

View file

@ -74,12 +74,13 @@
int xml_child_sort = 1;
/*! Given an XML object and a child name, return yang stmt of child
/*! Given a child name and an XML object, return yang stmt of child
* If no xml parent, find root yang stmt matching name
* @param[in] name Name of child
* @param[in] x Child
* @param[in] xp XML parent, can be NULL.
* @param[in] yspec Yang specification (top level)
* @param[out] yresult Pointer to yang stmt of result, or NULL, if not found
* @note special rule for rpc, ie <rpc><foo>,look for top "foo" node.
*/
int
xml_child_spec(char *name,
@ -108,6 +109,7 @@ xml_child_spec(char *name,
* @retval >0 if arg1 is greater than arg2
* @note args are pointer ot pointers, to fit into qsort cmp function
* @see xml_cmp1 Similar, but for one object
* @note empty value/NULL is smallest value
*/
int
xml_cmp(const void* arg1,
@ -144,7 +146,12 @@ xml_cmp(const void* arg1,
return 0; /* Ordered by user: maintain existing order */
switch (y1->ys_keyword){
case Y_LEAF_LIST: /* Match with name and value */
equal = strcmp(xml_body(x1), xml_body(x2));
if ((b1 = xml_body(x1)) == NULL)
equal = -1;
else if ((b2 = xml_body(x2)) == NULL)
equal = 1;
else
equal = strcmp(b1, b2);
break;
case Y_LIST: /* Match with key values
* Use Y_LIST cache (see struct yang_stmt)
@ -168,7 +175,7 @@ xml_cmp(const void* arg1,
}
/*! Compare xml object
* @param[in] yangi Yang order
* @param[in] yangi Yang order
* @param[in] keynr Length of keyvec/keyval vector when applicable
* @param[in] keyvec Array of of yang key identifiers
* @param[in] keyval Array of of yang key values
@ -203,8 +210,10 @@ xml_cmp1(cxobj *x,
case Y_LEAF_LIST: /* Match with name and value */
if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL)
*userorder=1;
b=xml_body(x);
match = strcmp(keyval[0], b);
if ((b=xml_body(x)) == NULL)
match = 1;
else
match = strcmp(keyval[0], b);
break;
case Y_LIST: /* Match with array of key values */
if (userorder && yang_find((yang_node*)y, Y_ORDERED_BY, "user") != NULL)

View file

@ -62,7 +62,6 @@
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_xpath_parse.h"
#include "clixon_xpath_ctx.h"
#include "clixon_xpath.h"
@ -89,17 +88,6 @@ const map_str2int xpopmap[] = {
{NULL, -1}
};
/* Mapping between axis type string <--> int */
static const map_str2int axismap[] = {
{"self", A_SELF},
{"child", A_CHILD},
{"parent", A_PARENT},
{"root", A_ROOT},
{"ancestor", A_ANCESTOR},
{"descendant-or-self", A_DESCENDANT_OR_SELF},
{NULL, -1}
};
static const map_str2int xpath_tree_map[] = {
{"expr", XP_EXP},
{"andexpr", XP_AND},
@ -857,14 +845,14 @@ xp_eval(xp_ctx *xc,
xp_ctx *xr2 = NULL;
int use_xr0 = 0; /* In 2nd child use transitively result of 1st child */
if (debug){
if (debug>1){
cbuf *cb;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
ctx_print(cb, +2, xc, (char*)clicon_int2str(xpath_tree_map, xs->xs_type));
clicon_debug(1, "%s", cbuf_get(cb));
clicon_debug(2, "%s", cbuf_get(cb));
cbuf_free(cb);
}
/* Pre-actions before check first child c0
@ -1028,7 +1016,7 @@ xp_eval(xp_ctx *xc,
goto done;
}
ctx_print(cb, -2, *xrp, (char*)clicon_int2str(xpath_tree_map, xs->xs_type));
clicon_debug(1, "%s", cbuf_get(cb));
clicon_debug(2, "%s", cbuf_get(cb));
cbuf_free(cb);
}
retval = 0;
@ -1071,10 +1059,10 @@ xpath_vec_ctx(cxobj *xcur,
clicon_err(OE_XML, 0, "XPATH parser error with no error code (should not happen)");
goto done;
}
if (debug){
if (debug > 1){
cbuf *cb = cbuf_new();
xpath_tree_print(cb, xy.xy_top);
clicon_debug(1, "xpath parse tree:\n%s", cbuf_get(cb));
clicon_debug(2, "xpath parse tree:\n%s", cbuf_get(cb));
cbuf_free(cb);
}
xc.xc_type = XT_NODESET;
@ -1142,16 +1130,11 @@ xpath_first(cxobj *xcur,
goto done;
}
va_end(ap);
#ifdef COMPAT_XSL
if ((cx = xpath_first_xsl(xcur, xpath)) == NULL)
goto done;
#else
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET && xr->xc_size)
cx = xr->xc_nodeset[0];
#endif
done:
if (xr)
ctx_free(xr);
@ -1211,10 +1194,6 @@ xpath_vec(cxobj *xcur,
va_end(ap);
*vec=NULL;
*veclen = 0;
#ifdef COMPAT_XSL
if (xpath_vec_xsl(xcur, xpath, vec, veclen) < 0)
goto done;
#else
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET){
@ -1222,7 +1201,6 @@ xpath_vec(cxobj *xcur,
xr->xc_nodeset = NULL;
*veclen = xr->xc_size;
}
#endif
retval = 0;
done:
if (xr)
@ -1268,10 +1246,8 @@ xpath_vec_flag(cxobj *xcur,
size_t len;
char *xpath = NULL;
xp_ctx *xr = NULL;
#ifndef COMPAT_XSL
int i;
cxobj *x;
#endif
va_start(ap, veclen);
len = vsnprintf(NULL, 0, format, ap);
@ -1291,10 +1267,6 @@ xpath_vec_flag(cxobj *xcur,
va_end(ap);
*vec=NULL;
*veclen = 0;
#ifdef COMPAT_XSL
if (xpath_vec_flag_xsl(xcur, xpath, flags, vec, veclen) < 0)
goto done;
#else
if (xpath_vec_ctx(xcur, xpath, &xr) < 0)
goto done;
if (xr && xr->xc_type == XT_NODESET){
@ -1305,8 +1277,6 @@ xpath_vec_flag(cxobj *xcur,
goto done;
}
}
#endif
retval = 0;
done:
if (xr)

View file

@ -193,85 +193,85 @@ xp_new(enum xp_type type,
/*
*/
start : expr X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->expr"); YYACCEPT; }
| locationpath X_EOF { _XY->xy_top=$1;clicon_debug(1,"start->locationpath"); YYACCEPT; }
start : expr X_EOF { _XY->xy_top=$1;clicon_debug(2,"start->expr"); YYACCEPT; }
| locationpath X_EOF { _XY->xy_top=$1;clicon_debug(2,"start->locationpath"); YYACCEPT; }
;
expr : expr LOGOP andexpr { $$=xp_new(XP_EXP,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"expr->expr or andexpr"); }
| andexpr { $$=xp_new(XP_EXP,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"expr-> andexpr"); }
expr : expr LOGOP andexpr { $$=xp_new(XP_EXP,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"expr->expr or andexpr"); }
| andexpr { $$=xp_new(XP_EXP,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"expr-> andexpr"); }
;
andexpr : andexpr LOGOP relexpr { $$=xp_new(XP_AND,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"andexpr-> andexpr and relexpr"); }
| relexpr { $$=xp_new(XP_AND,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"andexpr-> relexpr"); }
andexpr : andexpr LOGOP relexpr { $$=xp_new(XP_AND,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"andexpr-> andexpr and relexpr"); }
| relexpr { $$=xp_new(XP_AND,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"andexpr-> relexpr"); }
;
relexpr : relexpr RELOP addexpr { $$=xp_new(XP_RELEX,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"relexpr-> relexpr relop addexpr"); }
| addexpr { $$=xp_new(XP_RELEX,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"relexpr-> addexpr"); }
relexpr : relexpr RELOP addexpr { $$=xp_new(XP_RELEX,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"relexpr-> relexpr relop addexpr"); }
| addexpr { $$=xp_new(XP_RELEX,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"relexpr-> addexpr"); }
;
addexpr : addexpr ADDOP unionexpr { $$=xp_new(XP_ADD,$2,0.0,NULL,NULL,$1, $3);clicon_debug(1,"addexpr-> addexpr ADDOP unionexpr"); }
| unionexpr { $$=xp_new(XP_ADD,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"addexpr-> unionexpr"); }
addexpr : addexpr ADDOP unionexpr { $$=xp_new(XP_ADD,$2,0.0,NULL,NULL,$1, $3);clicon_debug(2,"addexpr-> addexpr ADDOP unionexpr"); }
| unionexpr { $$=xp_new(XP_ADD,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"addexpr-> unionexpr"); }
;
/* node-set */
unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"unionexpr-> unionexpr | pathexpr"); }
| pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"unionexpr-> pathexpr"); }
unionexpr : unionexpr '|' pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(2,"unionexpr-> unionexpr | pathexpr"); }
| pathexpr { $$=xp_new(XP_UNION,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"unionexpr-> pathexpr"); }
;
pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> locationpath"); }
| primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(1,"pathexpr-> primaryexpr"); }
pathexpr : locationpath { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"pathexpr-> locationpath"); }
| primaryexpr { $$=xp_new(XP_PATHEXPR,A_NAN,0.0,NULL,NULL,$1, NULL);clicon_debug(2,"pathexpr-> primaryexpr"); }
;
/* location path returns a node-set */
locationpath : rellocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> rellocpath"); }
| abslocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"locationpath-> abslocpath"); }
locationpath : rellocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(2,"locationpath-> rellocpath"); }
| abslocpath { $$=xp_new(XP_LOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(2,"locationpath-> abslocpath"); }
;
abslocpath : '/' { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,NULL, NULL);clicon_debug(1,"abslocpath-> /"); }
| '/' rellocpath { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,$2, NULL);clicon_debug(1,"abslocpath->/ rellocpath");}
abslocpath : '/' { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,NULL, NULL);clicon_debug(2,"abslocpath-> /"); }
| '/' rellocpath { $$=xp_new(XP_ABSPATH,A_ROOT,0.0,NULL,NULL,$2, NULL);clicon_debug(2,"abslocpath->/ rellocpath");}
/* // is short for /descendant-or-self::node()/ */
| DOUBLESLASH rellocpath {$$=xp_new(XP_ABSPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$2, NULL); clicon_debug(1,"abslocpath-> // rellocpath"); }
| DOUBLESLASH rellocpath {$$=xp_new(XP_ABSPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$2, NULL); clicon_debug(2,"abslocpath-> // rellocpath"); }
;
rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(1,"rellocpath-> step"); }
| rellocpath '/' step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(1,"rellocpath-> rellocpath / step"); }
| rellocpath DOUBLESLASH step { $$=xp_new(XP_RELLOCPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$1, $3); clicon_debug(1,"rellocpath-> rellocpath // step"); }
rellocpath : step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, NULL); clicon_debug(2,"rellocpath-> step"); }
| rellocpath '/' step { $$=xp_new(XP_RELLOCPATH,A_NAN,0.0,NULL,NULL,$1, $3);clicon_debug(2,"rellocpath-> rellocpath / step"); }
| rellocpath DOUBLESLASH step { $$=xp_new(XP_RELLOCPATH,A_DESCENDANT_OR_SELF,0.0,NULL,NULL,$1, $3); clicon_debug(2,"rellocpath-> rellocpath // step"); }
;
step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,0.0, NULL, NULL, $2, $3);clicon_debug(1,"step->axisspec(%d) nodetest", $1); }
| '.' predicates { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> ."); }
| DOUBLEDOT predicates { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, $2); clicon_debug(1,"step-> .."); }
step : axisspec nodetest predicates {$$=xp_new(XP_STEP,$1,0.0, NULL, NULL, $2, $3);clicon_debug(2,"step->axisspec(%d) nodetest", $1); }
| '.' predicates { $$=xp_new(XP_STEP,A_SELF, 0.0,NULL, NULL, NULL, $2); clicon_debug(2,"step-> ."); }
| DOUBLEDOT predicates { $$=xp_new(XP_STEP, A_PARENT, 0.0,NULL, NULL, NULL, $2); clicon_debug(2,"step-> .."); }
;
axisspec : AXISNAME DOUBLECOLON { clicon_debug(1,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;}
| '@' { $$=A_ATTRIBUTE; clicon_debug(1,"axisspec-> @"); }
| { clicon_debug(1,"axisspec-> "); $$=A_CHILD;}
axisspec : AXISNAME DOUBLECOLON { clicon_debug(2,"axisspec-> AXISNAME(%d) ::", $1); $$=$1;}
| '@' { $$=A_ATTRIBUTE; clicon_debug(2,"axisspec-> @"); }
| { clicon_debug(2,"axisspec-> "); $$=A_CHILD;}
;
nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"nodetest-> *"); }
| NAME { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, $1, NULL, NULL); clicon_debug(1,"nodetest-> name(%s)",$1); }
| NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,0.0, $1, $3, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : name(%s)", $1, $3); }
| NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"nodetest-> name(%s) : *", $1); }
| NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,0.0, $1, NULL, NULL, NULL); clicon_debug(1,"nodetest-> nodetype()"); }
nodetest : '*' { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(2,"nodetest-> *"); }
| NAME { $$=xp_new(XP_NODE,A_NAN,0.0, NULL, $1, NULL, NULL); clicon_debug(2,"nodetest-> name(%s)",$1); }
| NAME ':' NAME { $$=xp_new(XP_NODE,A_NAN,0.0, $1, $3, NULL, NULL);clicon_debug(2,"nodetest-> name(%s) : name(%s)", $1, $3); }
| NAME ':' '*' { $$=xp_new(XP_NODE,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(2,"nodetest-> name(%s) : *", $1); }
| NODETYPE '(' ')' { $$=xp_new(XP_NODE_FN,A_NAN,0.0, $1, NULL, NULL, NULL); clicon_debug(2,"nodetest-> nodetype()"); }
;
/* evaluates to boolean */
predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, $1, $3); clicon_debug(1,"predicates-> [ expr ]"); }
| { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(1,"predicates->"); }
predicates : predicates '[' expr ']' { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, $1, $3); clicon_debug(2,"predicates-> [ expr ]"); }
| { $$=xp_new(XP_PRED,A_NAN,0.0, NULL, NULL, NULL, NULL); clicon_debug(2,"predicates->"); }
;
primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,0.0, NULL, NULL, $2, NULL); clicon_debug(1,"primaryexpr-> ( expr )"); }
| NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> NUMBER(%lf)", $1); }
| QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" string \""); }
| QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> \" \""); }
| APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' string '"); }
| APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> ' '"); }
| FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(1,"primaryexpr-> functionname ( arguments )"); }
primaryexpr : '(' expr ')' { $$=xp_new(XP_PRI0,A_NAN,0.0, NULL, NULL, $2, NULL); clicon_debug(2,"primaryexpr-> ( expr )"); }
| NUMBER { $$=xp_new(XP_PRIME_NR,A_NAN, $1, NULL, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> NUMBER(%lf)", $1); }
| QUOTE string QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> \" string \""); }
| QUOTE QUOTE { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> \" \""); }
| APOST string APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, $2, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> ' string '"); }
| APOST APOST { $$=xp_new(XP_PRIME_STR,A_NAN,0.0, NULL, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> ' '"); }
| FUNCTIONNAME '(' ')' { $$=xp_new(XP_PRIME_FN,A_NAN,0.0, $1, NULL, NULL, NULL);clicon_debug(2,"primaryexpr-> functionname ( arguments )"); }
;
/* XXX Adding this between FUNCTIONNAME() breaks parser,..
arguments : arguments expr { clicon_debug(1,"arguments-> arguments expr"); }
| { clicon_debug(1,"arguments-> "); }
arguments : arguments expr { clicon_debug(2,"arguments-> arguments expr"); }
| { clicon_debug(2,"arguments-> "); }
;
*/
string : string CHAR {
@ -279,9 +279,9 @@ string : string CHAR {
$$ = realloc($1, len+strlen($2) + 1);
sprintf($$+len, "%s", $2);
free($2);
clicon_debug(1,"string-> string CHAR");
clicon_debug(2,"string-> string CHAR");
}
| CHAR { clicon_debug(1,"string-> "); }
| CHAR { clicon_debug(2,"string-> "); }
;

File diff suppressed because it is too large Load diff

View file

@ -344,6 +344,8 @@ ys_dup(yang_stmt *old)
/*! Insert yang statement as child of a parent yang_statement, last in list
*
* @param[in] yn_parent Add child to this parent
* @param[in] ys_child Add this child
* Also add parent to child as up-pointer
*/
int
@ -607,7 +609,6 @@ yang_find_schemanode(yang_node *yn,
return ysmatch;
}
/*! Find first matching data node in all (sub)modules in a yang spec
*
* @param[in] ysp Yang specification
@ -620,39 +621,61 @@ yang_find_schemanode(yang_node *yn,
*/
yang_stmt *
yang_find_topnode(yang_spec *ysp,
char *argument,
char *nodeid,
yang_class class)
{
yang_stmt *ys = NULL;
yang_stmt *yc = NULL;
yang_stmt *ymod = NULL; /* module */
yang_stmt *yres = NULL; /* result */
char *prefix = NULL;
char *id = NULL;
int i;
for (i=0; i<ysp->yp_len; i++){
ys = ysp->yp_stmt[i];
switch (class){
case YC_NONE:
if ((yc = yang_find((yang_node*)ys, 0, argument)) != NULL)
return yc;
break;
case YC_DATANODE:
if ((yc = yang_find_datanode((yang_node*)ys, argument)) != NULL)
return yc;
break;
case YC_SCHEMANODE:
if ((yc = yang_find_schemanode((yang_node*)ys, argument)) != NULL)
return yc;
break;
case YC_DATADEFINITION:
break; /* nyi */
if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
goto done;
if (prefix){
if ((ymod = yang_find((yang_node*)ysp, Y_MODULE, prefix)) != NULL){
if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL)
goto ok;
goto done;
}
}
return NULL;
else /* No prefix given - loop through and find first */
for (i=0; i<ysp->yp_len; i++){
ymod = ysp->yp_stmt[i];
switch (class){
case YC_NONE:
if ((yres = yang_find((yang_node*)ymod, 0, id)) != NULL)
goto ok;
break;
case YC_DATANODE:
if ((yres = yang_find_datanode((yang_node*)ymod, id)) != NULL)
goto ok;
break;
case YC_SCHEMANODE:
if ((yres = yang_find_schemanode((yang_node*)ymod, id)) != NULL)
goto ok;
break;
case YC_DATADEFINITION:
break; /* nyi */
}
}
ok:
done:
if (prefix)
free(prefix);
if (id)
free(id);
return yres;
}
/*! Given a yang statement, find the prefix associated to this module
* @param[in] ys Yang statement
* @retval NULL Not found
* @retval prefix Prefix as char* pointer into yang tree
* @code
* char *myprefix;
* myprefix = yang_find_myprefix(ys);
* @endcode
*/
char *
yang_find_myprefix(yang_stmt *ys)
@ -738,7 +761,8 @@ yang_key2str(int keyword)
return (char*)clicon_int2str(ykmap, keyword);
}
/*! Find top module or sub-module. Note that ultimate top is yang spec
/*! Find top module or sub-module given a statement. Ultimate top is yang spec
* The routine recursively finds ancestors.
* @param[in] ys Any yang statement in a yang tree
* @retval ymod The top module or sub-module
* @see ys_spec
@ -822,13 +846,21 @@ yarg_prefix(yang_stmt *ys)
return prefix;
}
/*! Split yang node identifier into prefix and identifer.
* @param[in] node-id
* @param[out] prefix Malloced string. May be NULL.
* @param[out] id Malloced identifier.
* @retval 0 OK
* @retval -1 Error
* @code
* char *prefix = NULL;
* char *id = NULL;
* if (yang_nodeid_split(nodeid, &prefix, &id) < 0)
* goto done;
* if (prefix)
* free(prefix);
* if (id)
* free(id);
* @note caller need to free id and prefix after use
*/
int
@ -1251,11 +1283,11 @@ ys_populate_type(yang_stmt *ys,
* Do this recursively
* @param[in] ys The yang identity to populate.
* @param[in] arg If set contains a derived identifier
* @see validate_identityref which in runtime validates actual valoues
* @see validate_identityref which in runtime validates actual values
*/
static int
ys_populate_identity(yang_stmt *ys,
void *arg)
char *idref)
{
int retval = -1;
yang_stmt *yc = NULL;
@ -1265,7 +1297,6 @@ ys_populate_identity(yang_stmt *ys,
char *baseid;
char *prefix = NULL;
cbuf *cb = NULL;
char *idref = (char*)arg;
char *p;
if (idref == NULL){
@ -1325,31 +1356,91 @@ ys_populate_identity(yang_stmt *ys,
return retval;
}
/*! Populate yang feature statement - set cv to 1 if enabled
*
* @param[in] ys Feature yang statement to populate.
* @param[in] h Clicon handle
*/
static int
ys_populate_feature(clicon_handle h,
yang_stmt *ys)
{
int retval = -1;
cxobj *x;
yang_stmt *ymod;
int found = 0;
cg_var *cv;
char *module;
char *feature;
cxobj *x1;
/* Eg, when parsing the config xml itself */
if ((x = clicon_conf_xml(h)) == NULL)
goto ok;
if ((ymod = ys_module(ys)) == NULL){
clicon_err(OE_YANG, 0, "module not found");
goto done;
}
module = ymod->ys_argument;
feature = ys->ys_argument;
x1 = NULL;
while ((x1 = xml_child_each(x, x1, CX_ELMNT)) != NULL && found == 0) {
char *m = NULL;
char *f = NULL;
if (strcmp(xml_name(x1), "CLICON_FEATURE") != 0)
continue;
/* get m and f from configuration feature rules */
if (yang_nodeid_split(xml_body(x1), &m, &f) < 0)
goto done;
if (m && f &&
(strcmp(m,"*")==0 ||
strcmp(m, module)==0) &&
(strcmp(f,"*")==0 ||
strcmp(f, feature)==0))
found = 1;
if (m) free(m);
if (f) free(f);
}
if ((cv = cv_new(CGV_BOOL)) == NULL){
clicon_err(OE_YANG, errno, "cv_new");
goto done;
}
cv_name_set(cv, feature);
cv_bool_set(cv, found);
clicon_debug(1, "%s %s:%s %d", __FUNCTION__, module, feature, found);
ys->ys_cv = cv;
ok:
retval = 0;
done:
return retval;
}
/*! Populate with cligen-variables, default values, etc. Sanity checks on complete tree.
*
* We do this in 2nd pass after complete parsing to be sure to have a complete parse-tree
* See ys_parse_sub for first pass and what can be assumed
* After this pass, cv:s are set for LEAFs and LEAF-LISTs
*/
static int
int
ys_populate(yang_stmt *ys,
void *arg)
{
int retval = -1;
// clicon_handle h = (clicon_handle)arg;
switch(ys->ys_keyword){
case Y_LEAF:
case Y_LEAF_LIST:
if (ys_populate_leaf(ys, arg) < 0)
if (ys_populate_leaf(ys, NULL) < 0)
goto done;
break;
case Y_LIST:
if (ys_populate_list(ys, arg) < 0)
if (ys_populate_list(ys, NULL) < 0)
goto done;
break;
case Y_RANGE:
case Y_LENGTH:
if (ys_populate_range(ys, arg) < 0)
if (ys_populate_range(ys, NULL) < 0)
goto done;
break;
case Y_MANDATORY: /* call yang_mandatory() to check if set */
@ -1358,11 +1449,11 @@ ys_populate(yang_stmt *ys,
goto done;
break;
case Y_TYPE:
if (ys_populate_type(ys, arg) < 0)
if (ys_populate_type(ys, NULL) < 0)
goto done;
break;
case Y_IDENTITY:
if (ys_populate_identity(ys, arg) < 0)
if (ys_populate_identity(ys, NULL) < 0)
goto done;
break;
default:
@ -1373,7 +1464,6 @@ ys_populate(yang_stmt *ys,
return retval;
}
/*! Resolve a grouping name from a point in the yang tree
* @retval 0 OK, but ygrouping determines if a grouping was resolved or not
* @retval -1 Error, with clicon_err called
@ -1594,8 +1684,9 @@ yang_expand(yang_node *yn)
* @retval NULL Error encountered
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* yang_parse_file # Read yang file into a string
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* yang_parse_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
@ -1647,7 +1738,7 @@ yang_parse_str(char *str,
/*! Parse yang spec from an open file descriptor
* @param[in] fd File descriptor containing the YANG file as ASCII characters
* @param[in] name For debug, eg filename
* @param[in] ysp Yang specification. Should ave been created by caller using yspec_new
* @param[in] ysp Yang specification. Should have been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error
*/
@ -1695,49 +1786,6 @@ yang_parse_file(int fd,
return ymod; /* top-level (sub)module */
}
/*! Open a file, read into a string and invoke yang parsing
*
* Similar to clicon_yang_str(), just read a file first
* (cloned from cligen)
* @param[in] h CLICON handle
* @param[in] filename Name of file
* @param[in] ysp Yang specification. Should ave been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* The database symbols are inserted in alphabetical order.
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* yang_parse_file # Read yang file into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
static yang_stmt *
yang_parse_filename(const char *filename,
yang_spec *ysp)
{
yang_stmt *ymod = NULL;
int fd = -1;
struct stat st;
clicon_log(LOG_DEBUG, "Parsing yang file: %s", filename);
if (stat(filename, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", filename);
goto done;
}
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_YANG, errno, "open(%s)", filename);
goto done;
}
if ((ymod = yang_parse_file(fd, filename, ysp)) < 0)
goto done;
done:
if (fd != -1)
close(fd);
return ymod; /* top-level (sub)module */
}
/*! No specific revision give. Match a yang file given dir and module
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside
@ -1788,6 +1836,84 @@ yang_parse_find_match(const char *yang_dir,
return retval;
}
/*! Open a file, read into a string and invoke yang parsing
*
* Similar to clicon_yang_str(), just read a file first
* (cloned from cligen)
* @param[in] h CLICON handle
* @param[in] filename Name of file
* @param[in] ysp Yang specification. Should ave been created by caller using yspec_new
* @retval ymod Top-level yang (sub)module
* @retval NULL Error encountered
* The database symbols are inserted in alphabetical order.
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* yang_parse_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
static yang_stmt *
yang_parse_filename(const char *filename,
yang_spec *ysp)
{
yang_stmt *ymod = NULL;
int fd = -1;
struct stat st;
clicon_log(LOG_DEBUG, "Parsing yang file: %s", filename);
if (stat(filename, &st) < 0){
clicon_err(OE_YANG, errno, "%s not found", filename);
goto done;
}
if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_YANG, errno, "open(%s)", filename);
goto done;
}
if ((ymod = yang_parse_file(fd, filename, ysp)) < 0)
goto done;
done:
if (fd != -1)
close(fd);
return ymod; /* top-level (sub)module */
}
static yang_stmt *
yang_parse_module(const char *module,
const char *dir,
const char *revision,
yang_spec *ysp)
{
cbuf *fbuf = NULL;
int nr;
yang_stmt *ymod = NULL;
if ((fbuf = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
if (revision)
cprintf(fbuf, "%s/%s@%s.yang", dir, module, revision);
else{
/* No specific revision, Match a yang file */
if ((nr = yang_parse_find_match(dir, module, fbuf)) < 0)
goto done;
if (nr == 0){
clicon_err(OE_YANG, errno, "No matching %s yang files found in %s (expected module name or absolute filename)", module, dir);
goto done;
}
}
if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL)
goto done;
done:
if (fbuf)
cbuf_free(fbuf);
return ymod; /* top-level (sub)module */
}
/*! Parse one yang module then go through (sub)modules and parse them recursively
*
* @param[in] h CLICON handle
@ -1801,68 +1927,45 @@ yang_parse_find_match(const char *yang_dir,
* Calling order:
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules, parse them and then recursively parse them
* yang_parse_file # Read yang file into a string
* yang_parse_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
static yang_stmt *
yang_parse_recurse(const char *yang_dir,
const char *module,
const char *revision,
static int
yang_parse_recurse(yang_stmt *ymod,
const char *dir,
yang_spec *ysp)
{
int retval = -1;
yang_stmt *yi = NULL; /* import */
yang_stmt *ymod = NULL;
yang_stmt *yrev;
char *modname;
char *submodule;
char *subrevision;
cbuf *fbuf = NULL;
int nr;
if (module[0] == '/'){
if ((ymod = yang_parse_filename(module, ysp)) == NULL)
goto done;
}
else {
if ((fbuf = cbuf_new()) == NULL){
clicon_err(OE_YANG, errno, "cbuf_new");
goto done;
}
if (revision)
cprintf(fbuf, "%s/%s@%s.yang", yang_dir, module, revision);
else{
/* No specific revision, Match a yang file */
if ((nr = yang_parse_find_match(yang_dir, module, fbuf)) < 0)
goto done;
if (nr == 0){
clicon_err(OE_YANG, errno, "No matching %s yang files found in %s (expected module name or absolute filename)", module, yang_dir);
goto done;
}
}
if ((ymod = yang_parse_filename(cbuf_get(fbuf), ysp)) == NULL)
goto done;
}
yang_stmt *subymod;
/* go through all import statements of ysp (or its module) */
while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){
if (yi->ys_keyword != Y_IMPORT)
continue;
modname = yi->ys_argument;
submodule = yi->ys_argument;
if ((yrev = yang_find((yang_node*)yi, Y_REVISION_DATE, NULL)) != NULL)
subrevision = yrev->ys_argument;
else
subrevision = NULL;
if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL)
if (yang_find((yang_node*)ysp, Y_MODULE, submodule) == NULL){
/* recursive call */
if (yang_parse_recurse(yang_dir, modname, subrevision, ysp) == NULL){
if ((subymod = yang_parse_module(submodule, dir, subrevision, ysp)) == NULL)
goto done;
if (yang_parse_recurse(subymod, dir, ysp) < 0){
ymod = NULL;
goto done;
}
}
}
done:
if (fbuf)
cbuf_free(fbuf);
return ymod; /* top-level (sub)module */
retval = 0;
done:
return retval; /* top-level (sub)module */
}
int
@ -1908,13 +2011,92 @@ ys_schemanode_check(yang_stmt *ys,
return retval;
}
/*! Find feature and if-feature nodes, check features and remove disabled nodes
* @retval -1 Error
* @retval 0 Feature not enabled: remove yt
* @retval 1 OK
* @note On return 1 the over-lying function need to remove yt from its parent
* @note cannot use yang_apply here since child-list is modified (destructive)
*/
static int
yang_features(clicon_handle h,
yang_stmt *yt)
{
int retval = -1;
int i;
int j;
yang_stmt *ys = NULL;
char *prefix = NULL;
char *feature = NULL;
yang_stmt *ymod; /* module yang node */
yang_stmt *yfeat; /* feature yang node */
i = 0;
while (i<yt->ys_len){ /* Note, children may be removed */
ys = yt->ys_stmt[i];
if (ys->ys_keyword == Y_IF_FEATURE){
if (yang_nodeid_split(ys->ys_argument, &prefix, &feature) < 0)
goto done;
/* Specifically need to handle? strcmp(prefix, myprefix)) */
if (prefix == NULL)
ymod = ys_module(ys);
else
ymod = yang_find_module_by_prefix(yt, prefix);
/* Check if feature exists, and is set, otherwise remove */
if ((yfeat = yang_find((yang_node*)ymod, Y_FEATURE, feature)) == NULL ||
yfeat->ys_cv == NULL || !cv_bool_get(yfeat->ys_cv)){
retval = 0; /* feature not enabled */
goto done;
}
if (prefix){
free(prefix);
prefix = NULL;
}
if (feature){
free(feature);
feature = NULL;
}
}
else
if (ys->ys_keyword == Y_FEATURE){
if (ys_populate_feature(h, ys) < 0)
goto done;
} else switch (yang_features(h, ys)){
case -1: /* error */
goto done;
break;
case 0: /* disabled: remove ys */
for (j=i+1; j<yt->ys_len; j++)
yt->ys_stmt[j-1] = yt->ys_stmt[j];
yt->ys_len--;
yt->ys_stmt[yt->ys_len] = NULL;
ys_free(ys);
continue; /* Don't increment i */
break;
default: /* ok */
break;
}
i++;
}
retval = 1;
done:
if (prefix)
free(prefix);
if (feature)
free(feature);
return retval;
}
/*! Parse top yang module including all its sub-modules. Expand and populate yang tree
*
* @param[in] h CLICON handle
* @param[in] yang_dir Directory where all YANG module files reside (except mainfile)
* @param[in] mainmod Name of main YANG module. Or absolute file name.
* @param[in] filename File name containing Yang specification. Overrides module
* @param[in] module Name of main YANG module. Or absolute file name.
* @param[in] revision Main module revision date string or NULL
* @param[out] ysp Yang specification. Should ave been created by caller using yspec_new
* @param[in,out] ysp Yang specification. Should have been created by caller using yspec_new
* @param[out] ymodp Yang module of first, topmost Yang module, if given.
* @retval 0 Everything OK
* @retval -1 Error encountered
* The database symbols are inserted in alphabetical order.
@ -1924,53 +2106,79 @@ ys_schemanode_check(yang_stmt *ys,
* yang_parse # Parse top-level yang module. Expand and populate yang tree
* yang_parse_recurse # Parse one yang module, go through its (sub)modules,
* parse them and then recursively parse them
* yang_parse_file # Read yang file into a string
* yang_parse_filename # Read yang file into a string
* yang_parse_file # Read yang open file descriptor into a string
* yang_parse_str # Set up yacc parser and call it given a string
* clixon_yang_parseparse # Actual yang parsing using yacc
*/
int
yang_parse(clicon_handle h,
const char *yang_dir,
const char *mainmodule,
const char *filename,
const char *module,
const char *dir,
const char *revision,
yang_spec *ysp)
yang_spec *ysp,
yang_stmt **ymodp)
{
int retval = -1;
yang_stmt *ymod; /* Top-level yang (sub)module */
yang_stmt *ymod = NULL; /* Top-level yang (sub)module */
int i;
int modnr; /* Existing number of modules */
modnr = ysp->yp_len;
if (filename){
if ((ymod = yang_parse_filename(filename, ysp)) == NULL)
goto done;
}
else
if ((ymod = yang_parse_module(module, dir, revision, ysp)) == NULL)
goto done;
/* From here on, apply actions on new modules, ie ones after modnr. */
/* Step 1: parse from text to yang parse-tree. */
if ((ymod = yang_parse_recurse(yang_dir, mainmodule, revision, ysp)) == NULL)
goto done;
/* Add top module name as dbspec-name */
clicon_dbspec_name_set(h, ymod->ys_argument);
/* Step 2: Go through parse tree and populate it with cv types */
if (yang_apply((yang_node*)ysp, -1, ys_populate, NULL) < 0)
/* Iterate through modules */
if (yang_parse_recurse(ymod, dir, ysp) < 0)
goto done;
/* Step 3: Resolve all types: populate type caches. Requires eg length/range cvecs
/* Step 2: check features: check if enabled and remove disabled features */
for (i=modnr; i<ysp->yp_len; i++) /* XXX */
if (yang_features(h, ysp->yp_stmt[i]) < 0)
goto done;
/* Step 3: Go through parse tree and populate it with cv types */
for (i=modnr; i<ysp->yp_len; i++)
if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_populate, (void*)h) < 0)
goto done;
/* Step 4: Resolve all types: populate type caches. Requires eg length/range cvecs
* from ys_populate step
*/
yang_apply((yang_node*)ysp, Y_TYPE, ys_resolve_type, NULL);
for (i=modnr; i<ysp->yp_len; i++)
yang_apply((yang_node*)ysp->yp_stmt[i], Y_TYPE, ys_resolve_type, NULL);
/* Up to here resolving is made in the context they are defined, rather than the
context they are used. Like static scoping. After this we expand all
grouping/uses and unfold all macros into a single tree as they are used.
*/
/* Step 4: Macro expansion of all grouping/uses pairs. Expansion needs marking */
if (yang_expand((yang_node*)ysp) < 0)
goto done;
yang_apply((yang_node*)ymod, -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
/* Step 5: Macro expansion of all grouping/uses pairs. Expansion needs marking */
for (i=modnr; i<ysp->yp_len; i++){
if (yang_expand((yang_node*)ysp->yp_stmt[i]) < 0)
goto done;
yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_flag_reset, (void*)YANG_FLAG_MARK);
}
/* Step 4: Top-level augmentation of all modules */
/* Step 6: Top-level augmentation of all modules XXX: only new modules? */
if (yang_augment_spec(ysp) < 0)
goto done;
/* sanity check of schemanode references, need more here */
if (yang_apply((yang_node*)ysp, -1, ys_schemanode_check, NULL) < 0)
goto done;
/* Step 7: sanity check of schemanode references, need more here */
for (i=modnr; i<ysp->yp_len; i++)
if (yang_apply((yang_node*)ysp->yp_stmt[i], -1, ys_schemanode_check, NULL) < 0)
goto done;
if (ymodp)
*ymodp = ymod;
retval = 0;
done:
return retval;
@ -2388,55 +2596,83 @@ yang_config(yang_stmt *ys)
return 1;
}
/*! Parse netconf yang spec, used by netconf client and as internal protocol */
yang_spec *
yang_spec_netconf(clicon_handle h)
/*! Parse yang specification and its dependencies recursively given module
* @param[in] h clicon handle
* @param[in] module Module name, or absolute filename (including dir)
* @param[in] dir Directory where to look for modules and sub-modules
* @param[in] revision Revision, or NULL
* @param[in,out] yspec Modules parse are added to this yangspec
* @param[out] ymodp Yang module of first, topmost Yang module, if given.
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_file
*/
int
yang_spec_parse_module(clicon_handle h,
char *module,
char *dir,
char *revision,
yang_spec *yspec,
yang_stmt **ymodp)
{
yang_spec *yspec = NULL;
if ((yspec = yspec_new()) == NULL)
goto done;
if (yang_parse(h, CLIXON_DATADIR, "ietf-netconf", NULL, yspec) < 0){
yspec_free(yspec); yspec = NULL;
int retval = -1;
if (yspec == NULL){
clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
goto done;
}
clicon_netconf_yang_set(h, yspec);
done:
return yspec;
if (module == NULL){
clicon_err(OE_YANG, EINVAL, "yang module not set");
goto done;
}
/* Sanity check - use yang_spec_parse_file instead */
if (strlen(module) && module[0] == '/'){
clicon_err(OE_YANG, EINVAL, "yang module illegal format");
goto done;
}
if (dir == NULL){
clicon_err(OE_YANG, EINVAL, "yang dir not set");
goto done;
}
if (yang_parse(h, NULL, module, dir, revision, yspec, ymodp) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Read, parse and save application yang specification as option
* @param[in] h clicon handle
* @param[in] f file to print to (if printspec enabled)
* @param[in] printspec print database (YANG) specification as read from file
/*! Parse yang specification and its dependencies recursively given filename
* @param[in] h clicon handle
* @param[in] filename Actual filename (including dir and revision)
* @param[in] dir Directory for sub-modules
* @param[in,out] yspec Modules parse are added to this yangspec
* @param[out] ymodp Yang module of first, topmost Yang module, if given.
* @retval 0 OK
* @retval -1 Error
* @see yang_spec_parse_module for yang dir,module,revision instead of actual filename
*/
yang_spec*
yang_spec_main(clicon_handle h)
int
yang_spec_parse_file(clicon_handle h,
char *filename,
char *dir,
yang_spec *yspec,
yang_stmt **ymodp)
{
yang_spec *yspec = NULL;
char *yang_dir;
char *yang_module;
char *yang_revision;
if ((yang_dir = clicon_yang_dir(h)) == NULL){
clicon_err(OE_FATAL, 0, "CLICON_YANG_DIR option not set");
int retval = -1;
if (yspec == NULL){
clicon_err(OE_YANG, EINVAL, "yang spec is NULL");
goto done;
}
/* Yang module is either specific absolute filename, or main module */
if ((yang_module = clicon_yang_module_main(h)) == NULL){
clicon_err(OE_FATAL, 0, "CLICON_YANG_MODULE_MAIN option not set");
if (dir == NULL){
clicon_err(OE_YANG, EINVAL, "yang dir not set");
goto done;
}
yang_revision = clicon_yang_module_revision(h);
if ((yspec = yspec_new()) == NULL)
if (yang_parse(h, filename, NULL, dir, NULL, yspec, ymodp) < 0)
goto done;
if (yang_parse(h, yang_dir, yang_module, yang_revision, yspec) < 0){
yspec_free(yspec); yspec = NULL;
goto done;
}
clicon_dbspec_yang_set(h, yspec);
retval = 0;
done:
return yspec;
return retval;
}
/*! Given a yang node, translate the argument string to a cv vector

View file

@ -0,0 +1,242 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 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 *****
* Yang module and feature handling
* @see https://tools.ietf.org/html/rfc7895
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <regex.h>
#include <dirent.h>
#include <sys/types.h>
#include <fcntl.h>
#include <syslog.h>
#include <assert.h>
#include <sys/stat.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_log.h"
#include "clixon_err.h"
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_file.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_options.h"
#include "clixon_netconf_lib.h"
#include "clixon_yang_module.h"
/*! Init the Yang module library
*
* Load RFC7895 yang spec, module-set-id, etc.
* @param[in] h Clicon handle
* @note CLIXON_DATADIR is hardcoded
*/
int
yang_modules_init(clicon_handle h)
{
int retval = -1;
yang_spec *yspec;
yspec = clicon_dbspec_yang(h);
if (!clicon_option_bool(h, "CLICON_MODULE_LIBRARY_RFC7895"))
goto ok;
/* Ensure module-set-id is set */
if (!clicon_option_exists(h, "CLICON_MODULE_SET_ID")){
clicon_err(OE_CFG, ENOENT, "CLICON_MODULE_SET_ID must be defined when CLICON_MODULE_LIBRARY_RFC7895 is enabled");
goto done;
}
/* Ensure revision exists is set */
if (yang_spec_parse_module(h, "ietf-yang-library", CLIXON_DATADIR, NULL, yspec, NULL)< 0)
goto done;
/* Find revision */
if (yang_modules_revision(h) == NULL){
clicon_err(OE_CFG, ENOENT, "Yang client library yang spec has no revision");
goto done;
}
ok:
retval = 0;
done:
return retval;
}
/*! Return RFC7895 revision (if parsed)
* @param[in] h Clicon handle
* @retval revision String (dont free)
* @retval NULL Error: RFC7895 not loaded or revision not found
*/
char *
yang_modules_revision(clicon_handle h)
{
yang_spec *yspec;
yang_stmt *ymod;
yang_stmt *yrev;
char *revision = NULL;
yspec = clicon_dbspec_yang(h);
if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, "ietf-yang-library")) != NULL){
if ((yrev = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL){
revision = yrev->ys_argument;
}
}
return revision;
}
/*! Get modules state according to RFC 7895
* @param[in] h Clicon handle
* @param[in] yspec Yang spec
* @param[in,out] xret Existing XML tree, merge x into this
* @retval -1 Error (fatal)
* @retval 0 OK
* @retval 1 Statedata callback failed
* @notes NYI: schema, deviation
x +--ro modules-state
x +--ro module-set-id string
x +--ro module* [name revision]
x +--ro name yang:yang-identifier
x +--ro revision union
+--ro schema? inet:uri
x +--ro namespace inet:uri
+--ro feature* yang:yang-identifier
+--ro deviation* [name revision]
| +--ro name yang:yang-identifier
| +--ro revision union
+--ro conformance-type enumeration
+--ro submodule* [name revision]
+--ro name yang:yang-identifier
+--ro revision union
+--ro schema? inet:uri
* @see netconf_create_hello
*/
int
yang_modules_state_get(clicon_handle h,
yang_spec *yspec,
cxobj **xret)
{
int retval = -1;
cxobj *x = NULL;
cbuf *cb = NULL;
yang_stmt *ylib = NULL; /* ietf-yang-library */
yang_stmt *yns = NULL; /* namespace */
yang_stmt *ymod; /* generic module */
yang_stmt *ys;
yang_stmt *yc;
char *module_set_id;
char *module = "ietf-yang-library";
module_set_id = clicon_option_str(h, "CLICON_MODULE_SET_ID");
if ((ylib = yang_find((yang_node*)yspec, Y_MODULE, module)) == NULL){
clicon_err(OE_YANG, 0, "%s not found", module);
goto done;
}
if ((yns = yang_find((yang_node*)ylib, Y_NAMESPACE, NULL)) == NULL){
clicon_err(OE_YANG, 0, "%s yang namespace not found", module);
goto done;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, 0, "clicon buffer");
goto done;
}
cprintf(cb,"<modules-state xmlns=\"%s\">", yns->ys_argument);
cprintf(cb,"<module-set-id>%s</module-set-id>", module_set_id);
ymod = NULL;
while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) {
if (ymod->ys_keyword != Y_MODULE)
continue;
cprintf(cb,"<module>");
cprintf(cb,"<name>%s</name>", ymod->ys_argument);
if ((ys = yang_find((yang_node*)ymod, Y_REVISION, NULL)) != NULL)
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
if ((ys = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) != NULL)
cprintf(cb,"<namespace>%s</namespace>", ys->ys_argument);
else
cprintf(cb,"<namespace></namespace>");
cprintf(cb, "<conformance-type>implement</conformance-type>");
yc = NULL;
while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) {
switch(yc->ys_keyword){
case Y_FEATURE:
if (yc->ys_cv && cv_bool_get(yc->ys_cv))
cprintf(cb,"<feature>%s</feature>", yc->ys_argument);
break;
case Y_SUBMODULE:
cprintf(cb,"<submodule>");
cprintf(cb,"<name>%s</name>", yc->ys_argument);
if ((ys = yang_find((yang_node*)yc, Y_REVISION, NULL)) != NULL)
cprintf(cb,"<revision>%s</revision>", ys->ys_argument);
else
cprintf(cb,"<revision></revision>");
cprintf(cb,"</submodule>");
break;
default:
break;
}
}
cprintf(cb,"</module>");
}
cprintf(cb,"</modules-state>");
if (xml_parse_string(cbuf_get(cb), yspec, &x) < 0){
if (netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
goto done;
retval = 1;
goto done;
}
retval = netconf_trymerge(x, yspec, xret);
done:
if (cb)
cbuf_free(cb);
if (x)
xml_free(x);
return retval;
}

View file

@ -111,6 +111,7 @@ clixon_yang_parsewrap(void)
<KEYWORD>\} { return *yytext; }
/* RFC 6020 keywords */
<KEYWORD>action { BEGIN(ARGUMENT); return K_ACTION; }
<KEYWORD>anyxml { BEGIN(ARGUMENT); return K_ANYXML; }
<KEYWORD>argument { BEGIN(ARGUMENT); return K_ARGUMENT; }
<KEYWORD>augment { BEGIN(ARGUMENT); return K_AUGMENT; }

View file

@ -64,6 +64,7 @@
* - Dont want to expose a generated yacc file to the API
* - Cant use the symbols in this file because yacc needs token definitions
*/
%token K_ACTION
%token K_ANYXML
%token K_ARGUMENT
%token K_AUGMENT
@ -417,6 +418,7 @@ submodule_header_stmts : submodule_header_stmts submodule_header_stmt
submodule_header_stmt : yang_version_stmt
{ clicon_debug(2,"submodule-header-stmt -> yang-version-stmt"); }
| belongs_to_stmt { clicon_debug(2,"submodule-header-stmt -> belongs-to-stmt"); }
;
/* linkage */
@ -485,6 +487,7 @@ body_stmt : extension_stmt { clicon_debug(2,"body-stmt -> extension-st
| data_def_stmt { clicon_debug(2,"body-stmt -> data-def-stmt");}
| augment_stmt { clicon_debug(2,"body-stmt -> augment-stmt");}
| rpc_stmt { clicon_debug(2,"body-stmt -> rpc-stmt");}
| notification_stmt { clicon_debug(2,"body-stmt -> notification-stmt");}
;
data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> container-stmt");}
@ -521,7 +524,9 @@ container_substmt : when_stmt { clicon_debug(2,"container-substmt -> when-
| reference_stmt { clicon_debug(2,"container-substmt -> reference-stmt"); }
| typedef_stmt { clicon_debug(2,"container-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"container-substmt -> grouping-stmt"); }
| data_def_stmt { clicon_debug(2,"container-substmt -> data-def-stmt");}
| data_def_stmt { clicon_debug(2,"container-substmt -> data-def-stmt");}
| action_stmt { clicon_debug(2,"container-substmt -> action-stmt");}
| notification_stmt { clicon_debug(2,"container-substmt -> notification-stmt");}
| unknown_stmt { clicon_debug(2,"container-substmt -> unknown-stmt");}
| { clicon_debug(2,"container-substmt ->");}
;
@ -619,6 +624,8 @@ list_substmt : when_stmt { clicon_debug(2,"list-substmt -> when-stmt
| typedef_stmt { clicon_debug(2,"list-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"list-substmt -> grouping-stmt"); }
| data_def_stmt { clicon_debug(2,"list-substmt -> data-def-stmt"); }
| action_stmt { clicon_debug(2,"list-substmt -> action-stmt"); }
| notification_stmt { clicon_debug(2,"list-substmt -> notification-stmt"); }
| unknown_stmt { clicon_debug(2,"list-substmt -> unknown-stmt");}
| { clicon_debug(2,"list-substmt -> "); }
;
@ -792,7 +799,9 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s
| description_stmt { clicon_debug(2,"augment-substmt -> description-stmt"); }
| reference_stmt { clicon_debug(2,"augment-substmt -> reference-stmt"); }
| data_def_stmt { clicon_debug(2,"augment-substmt -> data-def-stmt"); }
| case_stmt { clicon_debug(2,"augment-substmt -> case-stmt");}
| case_stmt { clicon_debug(2,"augment-substmt -> case-stmt");}
| action_stmt { clicon_debug(2,"augment-substmt -> action-stmt");}
| notification_stmt { clicon_debug(2,"augment-substmt -> notification-stmt");}
| { clicon_debug(2,"augment-substmt -> "); }
;
@ -846,6 +855,17 @@ rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stm
| { clicon_debug(2,"rpc-substmt -> "); }
;
/* action */
action_stmt : K_ACTION id_arg_str ';'
{ if (ysp_add(_yy, Y_ACTION, $2, NULL) == NULL) _YYERROR("39");
clicon_debug(2,"action-stmt -> ACTION id-arg-str ;"); }
| K_ACTION id_arg_str
{ if (ysp_add_push(_yy, Y_ACTION, $2) == NULL) _YYERROR("40"); }
'{' rpc_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("41");
clicon_debug(2,"action-stmt -> ACTION id-arg-str { rpc-substmts }"); }
;
/* input */
input_stmt : K_INPUT
{ if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); }
@ -875,6 +895,34 @@ output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */
;
/* notification */
notification_stmt : K_NOTIFICATION id_arg_str ';'
{ if (ysp_add(_yy, Y_NOTIFICATION, $2, NULL) == NULL) _YYERROR("46");
clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str ;"); }
| K_NOTIFICATION id_arg_str
{ if (ysp_add_push(_yy, Y_NOTIFICATION, $2) == NULL) _YYERROR("47"); }
'{' notification_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("48");
clicon_debug(2,"notification-stmt -> NOTIFICATION id-arg-str { notification-substmts }"); }
;
notification_substmts : notification_substmts notification_substmt
{ clicon_debug(2,"notification-substmts -> notification-substmts notification-substmt"); }
| notification_substmt
{ clicon_debug(2,"notification-substmts -> notification-substmt"); }
;
notification_substmt : if_feature_stmt { clicon_debug(2,"notification-substmt -> if-feature-stmt"); }
| must_stmt { clicon_debug(2,"notification-substmt -> must-stmt"); }
| status_stmt { clicon_debug(2,"notification-substmt -> status-stmt"); }
| description_stmt { clicon_debug(2,"notification-substmt -> description-stmt"); }
| reference_stmt { clicon_debug(2,"notification-substmt -> reference-stmt"); }
| typedef_stmt { clicon_debug(2,"notification-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"notification-substmt -> grouping-stmt"); }
| data_def_stmt { clicon_debug(2,"notification-substmt -> data-def-stmt"); }
| { clicon_debug(2,"notification-substmt -> "); }
;
/* Typedef */
typedef_stmt : K_TYPEDEF id_arg_str
{ if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("46"); }
@ -964,6 +1012,8 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st
| typedef_stmt { clicon_debug(2,"grouping-substmt -> typedef-stmt"); }
| grouping_stmt { clicon_debug(2,"grouping-substmt -> grouping-stmt"); }
| data_def_stmt { clicon_debug(2,"grouping-substmt -> data-def-stmt"); }
| action_stmt { clicon_debug(2,"grouping-substmt -> action-stmt"); }
| notification_stmt { clicon_debug(2,"grouping-substmt -> notification-stmt"); }
| { clicon_debug(2,"grouping-substmt -> "); }
;
@ -1310,6 +1360,15 @@ prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */
clicon_debug(2,"prefix-stmt -> PREFIX string ;");}
;
belongs_to_stmt : K_BELONGS_TO id_arg_str ';'
{ if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("100");
clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str ;");}
| K_BELONGS_TO id_arg_str '{' prefix_stmt '}'
{ if (ysp_add(_yy, Y_BELONGS_TO, $2, NULL)== NULL) _YYERROR("98");
clicon_debug(2,"belongs-to-stmt -> BELONGS-TO id-arg-str { prefix-stmt } ");}
;
description_stmt: K_DESCRIPTION string ';'
{ if (ysp_add(_yy, Y_DESCRIPTION, $2, NULL)== NULL) _YYERROR("101");
clicon_debug(2,"description-stmt -> DESCRIPTION string ;");}

View file

@ -4,8 +4,8 @@ This directory contains testing code for clixon and the example
routing application. Assumes setup of http daemon as describe under apps/restonf
- 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.
- test_auth.sh Auth tests using internal NACM
- test_auth_ext.sh Auth tests using external NACM
- test_nacm.sh Auth tests using internal NACM
- test_nacm_ext.sh Auth tests using external NACM (separate file)
- test_cli.sh CLI tests
- test_netconf.sh Netconf tests
- test_restconf.sh Restconf tests

View file

@ -1,4 +1,6 @@
#!/bin/bash
# Run, eg as:
# ./run.sh 2>&1 | tee test.log
# include err() and new() functions
. ./lib.sh

View file

@ -2,13 +2,11 @@
# Define test functions.
# Create working dir as variable "dir"
#set -e
testnr=0
testname=
# Set to 1 to enable old XSL implementation. Set to nothing, or comment if new.
# @see include/clixon_custom.h
#COMPAT_XSL=1
# For memcheck
#clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli"
clixon_cli=clixon_cli
@ -19,8 +17,8 @@ clixon_cli=clixon_cli
clixon_netconf=clixon_netconf
# How to run restconf stand-alone and using valgrind
#sudo su -c "/www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data
#sudo su -c "valgrind --leak-check=full --show-leak-kinds=all /www-data/clixon_restconf -f $cfg -D" -s /bin/sh www-data
#sudo su -c "/www-data/clixon_restconf -f $cfg -D 1" -s /bin/sh www-data
#sudo su -c "valgrind --trace-children=no --child-silent-after-fork=yes --leak-check=full --show-leak-kinds=all /www-data/clixon_restconf -f $cfg -D 1" -s /bin/sh www-data
#clixon_backend="valgrind --leak-check=full --show-leak-kinds=all clixon_backend"
clixon_backend=clixon_backend
@ -36,6 +34,7 @@ err(){
echo -e "\e[31m\nError in Test$testnr [$testname]:"
if [ $# -gt 0 ]; then
echo "Expected: $1"
echo
fi
if [ $# -gt 1 ]; then
echo "Received: $2"
@ -131,9 +130,10 @@ expecteof(){
ret=$($cmd<<EOF
$input
EOF
)
if [ $? -ne $retval ]; then
echo -e "\e[31m\nError in Test$testnr [$testname]:"
)
r=$?
if [ $r -ne $retval ]; then
echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:"
echo -e "\e[0m:"
exit -1
fi
@ -142,7 +142,7 @@ EOF
if [ -z "$ret" -a -z "$expect" ]; then
return
fi
match=`echo "$ret" | grep -Eo "$expect"`
match=`echo "$ret" | grep -GZo "$expect"`
# echo "ret:\"$ret\""
# echo "expect:\"$expect\""
# echo "match:\"$match\""
@ -178,13 +178,25 @@ expectwait(){
wait=$4
# Do while read stuff
sleep 10|cat <(echo $input) -| $cmd | while [ 1 ] ; do
read ret
echo timeout > /tmp/flag
ret=""
sleep $wait | cat <(echo $input) -| $cmd | while [ 1 ] ; do
read -t 20 r
# echo "r:$r"
ret="$ret$r"
match=$(echo "$ret" | grep -Eo "$expect");
if [ -z "$match" ]; then
err $expect "$ret"
echo error > /tmp/flag
err "$expect" "$ret"
else
echo ok > /tmp/flag # only this is OK
break;
fi
break
done
# cat /tmp/flag
if [ $(cat /tmp/flag) != "ok" ]; then
cat /tmp/flag
exit
fi
}

View file

@ -42,7 +42,6 @@ sudo $clixon_backend -s init -f $cfg
if [ $? -ne 0 ]; then
err
fi
new "cli tests"
new "cli configure top"
expectfn "$clixon_cli -1 -f $cfg set interfaces" 0 "^$"

172
test/test_feature.sh Executable file
View file

@ -0,0 +1,172 @@
#!/bin/bash
# Yang features. if-feature. and schema resources according to RFC7895
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
cat <<EOF > $cfg
<config>
<CLICON_FEATURE>$APPNAME:A</CLICON_FEATURE>
<CLICON_FEATURE>ietf-routing:router-id</CLICON_FEATURE>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/$APPNAME/yang</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>$APPNAME</CLICON_YANG_MODULE_MAIN>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
</config>
EOF
cat <<EOF > $fyang
module $APPNAME{
prefix ex;
import ietf-routing {
prefix rt;
}
feature A{
description "This test feature is enabled";
}
feature B{
description "This test feature is disabled";
}
feature C{
description "This test feature is disabled";
}
leaf x{
if-feature A;
type "string";
}
leaf y{
if-feature B;
type "string";
}
leaf z{
type "string";
}
}
EOF
new "start backend -s init -f $cfg -y $fyang"
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "cli enabled feature"
expectfn "$clixon_cli -1f $cfg -y $fyang set x foo" 0 ""
new "cli disabled feature"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set y foo" 255 "CLI syntax error: \"set y foo\": Unknown command"
new "cli enabled feature in other module"
expectfn "$clixon_cli -1f $cfg -y $fyang set routing routing-instance A router-id 1.2.3.4" 0 ""
new "cli disabled feature in other module"
expectfn "$clixon_cli -1f $cfg -l o -y $fyang set routing routing-instance A default-ribs" 255 "CLI syntax error: \"set routing routing-instance A default-ribs\": Unknown command"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf enabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><x>foo</x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate enabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf disabled feature"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config><A>foo</A></config></edit-config></rpc>]]>]]>" '^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>protocol</error-type><error-severity>error</error-severity><error-message>XML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?'
# This test has been broken up into all differetn modules instead of one large
# reply since the modules change so often
new "netconf schema resource, RFC 7895"
ret=$($clixon_netconf -qf $cfg -y $fyang<<EOF
<rpc><get><filter type="xpath" select="modules-state/module" xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-library"/></get></rpc>]]>]]>
EOF
)
new "netconf module A"
expect="<module><name>example</name><revision/><namespace/><feature>A</feature><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
if false ; then # clixon "config" bug
new "netconf module clixon-config"
expect="<module><name>clixon-config</name><revision>2018-09-30</revision><namespace/></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
fi # false
new "netconf module ietf-inet-types"
expect="<module><name>ietf-inet-types</name><revision>2013-07-15</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-inet-types</namespace><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "netconf module ietf-interfaces"
expect="<module><name>ietf-interfaces</name><revision>2014-05-08</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-interfaces</namespace><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "netconf module ietf-netconf"
expect="module><name>ietf-netconf</name><revision>2011-06-01</revision><namespace>urn:ietf:params:xml:ns:netconf:base:1.0</namespace><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "netconf module ietf-routing"
expect="<module><name>ietf-routing</name><revision>2014-10-26</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-routing</namespace><feature>router-id</feature><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
expect="<module><name>ietf-yang-library</name><revision>2016-06-21</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-yang-library</namespace><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "netconf module ietf-yang_types"
expect="<module><name>ietf-yang-types</name><revision>2013-07-15</revision><namespace>urn:ietf:params:xml:ns:yang:ietf-yang-types</namespace><conformance-type>implement</conformance-type></module>"
match=`echo "$ret" | grep -GZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "Kill backend"
# kill backend
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
# Check if still alive
pid=`pgrep clixon_backend`
if [ -n "$pid" ]; then
sudo kill $pid
fi
rm -rf $dir

View file

@ -11,7 +11,6 @@ cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>$fyang</CLICON_YANG_MODULE_MAIN>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
@ -163,22 +162,22 @@ new "netconf validate"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-tag>operation-failed</error-tag><error-type>application</error-type><error-severity>error</error-severity><error-message>Identityref validation failed, foo:bar not derived from crypto-alg</error-message></rpc-error></rpc-reply>]]>]]>$"
new "cli set crypto to mc:aes"
expectfn "$clixon_cli -1 -f $cfg -l o set crypto mc:aes" 0 "^$"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto mc:aes" 0 "^$"
new "cli validate"
expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
new "cli set crypto to aes"
expectfn "$clixon_cli -1 -f $cfg -l o set crypto aes" 0 "^$"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto aes" 0 "^$"
new "cli validate"
expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
new "cli set crypto to des:des3"
expectfn "$clixon_cli -1 -f $cfg -l o set crypto des:des3" 0 "^$"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$"
new "cli validate"
expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$"
expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$"
new "Kill backend"
# Check if still alive

View file

@ -5,8 +5,6 @@ APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
exit 0 # NYI
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
@ -91,6 +89,8 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candid
new "minmax: empty"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c/></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# NYI
if false; then
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
@ -117,7 +117,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><can
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
fi # NYI
# kill backend

Some files were not shown because too many files have changed in this diff Show more