Merge branch 'develop' for 3.8 release
This commit is contained in:
commit
40fac27c2e
120 changed files with 6814 additions and 2863 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
104
CHANGELOG.md
104
CHANGELOG.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
82
README.md
82
README.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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){
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 *);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
507
apps/restconf/restconf_stream.c
Normal file
507
apps/restconf/restconf_stream.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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
108
configure
vendored
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
31
configure.ac
31
configure.ac
|
|
@ -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>])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
97
doc/FAQ.md
97
doc/FAQ.md
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
116
lib/clixon/clixon_stream.h
Normal 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_ */
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
57
lib/clixon/clixon_yang_module.h
Normal file
57
lib/clixon/clixon_yang_module.h
Normal 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_ */
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
1049
lib/src/clixon_stream.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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("& ");
|
||||
len += strlen("&");
|
||||
break;
|
||||
case '<':
|
||||
if (strncmp(&str[i], "<![CDATA[", strlen("<![CDATA[")) == 0){
|
||||
|
|
@ -318,10 +364,10 @@ xml_chardata_encode(char *str,
|
|||
cdata++;
|
||||
}
|
||||
else
|
||||
len += strlen("< ");
|
||||
len += strlen("<");
|
||||
break;
|
||||
case '>':
|
||||
len += strlen("> ");
|
||||
len += strlen(">");
|
||||
break;
|
||||
default:
|
||||
len++;
|
||||
|
|
@ -349,7 +395,7 @@ xml_chardata_encode(char *str,
|
|||
else
|
||||
switch (str[i]){
|
||||
case '&':
|
||||
if ((l=snprintf(&esc[j], 7, "& ")) < 0){
|
||||
if ((l=snprintf(&esc[j], 6, "&")) < 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, "< ")) < 0){
|
||||
if ((l=snprintf(&esc[j], 5, "<")) < 0){
|
||||
clicon_err(OE_UNIX, errno, "snprintf");
|
||||
goto done;
|
||||
}
|
||||
j += l;
|
||||
break;
|
||||
case '>':
|
||||
if ((l=snprintf(&esc[j], 6, "> ")) < 0){
|
||||
if ((l=snprintf(&esc[j], 5, ">")) < 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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
%%
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
;
|
||||
|
||||
%%
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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-> "); }
|
||||
;
|
||||
|
||||
|
||||
|
|
|
|||
1041
lib/src/clixon_xsl.c
1041
lib/src/clixon_xsl.c
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
242
lib/src/clixon_yang_module.c
Normal file
242
lib/src/clixon_yang_module.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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 ;");}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#!/bin/bash
|
||||
# Run, eg as:
|
||||
# ./run.sh 2>&1 | tee test.log
|
||||
|
||||
# include err() and new() functions
|
||||
. ./lib.sh
|
||||
|
|
|
|||
40
test/lib.sh
40
test/lib.sh
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
172
test/test_feature.sh
Executable 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue