merged from develop branch: changed internal messages to netconf

This commit is contained in:
Olof hagsand 2017-04-07 12:38:25 +02:00
commit ff66577151
86 changed files with 4773 additions and 7720 deletions

View file

@ -29,21 +29,86 @@
#
# ***** END LICENSE BLOCK *****
- cli_copy_config added as generic cli command
- cli_show_config added as generic cli command
Replace all show_confv*() and show_conf*() with cli_show_config()
Example: replace:
show_confv_as_json("candidate","/sender");
with:
cli_show_config("candidate","json","/sender");
- Alternative yang spec option -y added to all applications
- Many clicon special string functions have been removed
- The netconf support has been extended with lock/unlock
- clicon_rpc_call() has been removed and should be replaced by extending the
internal netconf protocol.
See downcall() function in example/routing_cli.c and
routing_downcall() in example/routing_backend.c
- Replace clicon_rpc_xmlput with clicon_rpc_edit_config
- Removed xmldb daemon. All xmldb acceses is made backend daemon.
No direct accesses by clients to xmldb API.
Instead use the rpc calls in clixon_proto_client.[ch]
In clients (eg cli/netconf) replace xmldb_get() in client code with
clicon_rpc_get_config().
If you use the vector arguments of xmldb_get(), replace as follows:
xmldb_get(h, db, api_path, &xt, &xvec, &xlen);
with
clicon_rpc_get_config(h, dbstr, api_path, &xt);
xpath_vec(xt, api_path, &xvec, &xlen)
- clicon_rpc_change() is replaced with clicon_rpc_edit_config().
Note modify argument 5:
clicon_rpc_change(h, db, op, apipath, "value")
to:
clicon_rpc_edit_config(h, db, op, apipath, "<config>value</config>")
- xmdlb_put_xkey() and xmldb_put_tree() have been folded into xmldb_put()
Replace xmldb_put_xkey with xmldb_put as follows:
xmldb_put_xkey(h, "candidate", cbuf_get(cb), str, OP_REPLACE);
with
clicon_xml_parse(&xml, "<config>%s</config>", str);
xmldb_put(h, "candidate", OP_REPLACE, cbuf_get(cb), xml);
xml_free(xml);
- Change internal protocol from clicon_proto.h to netconf.
This means that the internal protocol defined in clixon_proto.[ch] is removed
- Netconf startup configuration support. Set CLICON_USE_STARTUP_CONFIG to 1 to
enable. Eg, if backend_main is started with -CIr startup will be copied to
running.
- Added ".." as valid step in xpath
- Use restconf format for internal xmldb keys. Eg /a/b=3,4
- List keys with special characters are RFC 3986 encoded.
- Changed example to use multiple cli callbacks
- Added cli multiple callback and expand support. Use options
CLICON_CLIGEN_CALLBACK_SINGLE_ARG and CLICON_CLIGEN_EXPAND_SINGLE_ARG
to control these.
The multiple support for expand callbacks is enabled but not for callbacks
since this causes problems for legacy applications.
If you change to multiple argument callbacks change all cli callback functions.
Library functions in clixon_cli_api.h (e.g cli_commit) is rewritten in new
for (eg cli_commitv). See clixon_cli_api.h for new names.
- List keys with special characters RFC 3986 encoded.
- Replaced cli expand functions with single to multiple args
This change is _not_ backward compatible
This effects all calls to expand_dbvar() or user-defined
expand callbacks
- Replaced cli callback functions with single arg to multiple args
This change is _not_ backward compatible.
You are affected if you
(1) use system callbacks (i.e. in clixon_cli_api.h)
(2) write your own cli callbacks
If you use cli callbacks, you need to rewrite cli callbacks from eg:
load("Comment") <filename:string>,load_config_file("filename replace");
to:
load("Comment") <filename:string>,load_config_file("filename", "replace");
If you write your own, you need to change the callback signature from;
int cli_callback(clicon_handle h, cvec *vars, cg_var *arg)
to:
int cli_callback(clicon_handle h, cvec *vars, cvec *argv)
and rewrite the code to handle argv instead of arg.
These are the system functions affected:
cli_set, cli_merge, cli_del, cli_debug_backend, cli_set_mode,
cli_start_shell, cli_quit, cli_commit, cli_validate, compare_dbs,
load_config_file, save_config_file, delete_all, discard_changes, cli_notify,
show_yang, show_conf_xpath
- Added --with-cligen and --with-qdbm configure options
- Added union type check for non-cli (eg xml) input
- Empty yang type. Relaxed yang types for unions, eg two strings with different length.

View file

@ -26,12 +26,11 @@ This README contains information for developers:
2. How to work in git (branching)
+++++++++++++++++++++++++++++++++
Baically follows: http://nvie.com/posts/a-successful-git-branching-model/
Basically follows: http://nvie.com/posts/a-successful-git-branching-model/
only somewhat simplified:
Do commits in develop branch. When done, merge with master.
$ git checkout develop
Switch to branch develop
$ git add ..

View file

@ -40,7 +40,7 @@ LIBS = @LIBS@
SHELL = /bin/sh
SUBDIRS = cli backend dbctrl netconf xmldb restconf
SUBDIRS = cli backend dbctrl netconf restconf
.PHONY: all clean depend install $(SUBDIRS)

File diff suppressed because it is too large Load diff

View file

@ -70,8 +70,6 @@ struct client_subscription{
* Prototypes
*/
int backend_client_rm(clicon_handle h, struct client_entry *ce);
int config_snapshot(clicon_handle h, char *dbname, char *dir);
int from_client(int fd, void *arg);
#endif /* _BACKEND_CLIENT_H_ */

View file

@ -126,6 +126,10 @@ generic_validate(yang_spec *yspec,
}
/*! Common code of candidate_validate and candidate_commit
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
* @retval 0 OK
* @retval -1 Fatal error or netconf error XXX Differentiate
*/
static int
validate_common(clicon_handle h,
@ -137,7 +141,6 @@ validate_common(clicon_handle h,
int i;
cxobj *xn;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
@ -160,7 +163,7 @@ validate_common(clicon_handle h,
&td->td_tcvec, /* changed: wanted values */
&td->td_clen) < 0)
goto done;
if (debug)
if (debug>1)
transaction_print(stderr, td);
/* Mark as changed in tree */
for (i=0; i<td->td_dlen; i++){ /* Also down */
@ -253,126 +256,145 @@ candidate_commit(clicon_handle h,
return retval;
}
/*! Do a diff between candidate and running, then start a validate transaction
*
* @param[in] h Clicon handle
* @param[in] candidate: The candidate database. The wanted backend state
*/
/*! Commit changes from candidate to running
* @param[in] h Clicon handle
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error
*/
int
candidate_validate(clicon_handle h,
char *candidate)
from_client_commit(clicon_handle h,
int mypid,
cbuf *cbret)
{
int retval = -1;
transaction_data_t *td = NULL;
int retval = -1;
int piddb;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "running");
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (candidate_commit(h, "candidate") < 0){
clicon_debug(1, "Commit candidate failed");
/* XXX: candidate_validate should have proper error handling */
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-attribute</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"</rpc-error></rpc-reply>",
clicon_err_reason);
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
// done:
return retval; /* may be zero if we ignoring errors from commit */
} /* from_client_commit */
/*! Discard all changes in candidate / revert to running
* @param[in] h Clicon handle
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error
*/
int
from_client_discard_changes(clicon_handle h,
int mypid,
cbuf *cbret)
{
int retval = -1;
int piddb;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "candidate");
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (xmldb_copy(h, "running", "candidate") < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>application</error-type>"
"<error-severity>error</error-severity>"
"<error-info>read-registry</error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
// done:
return retval; /* may be zero if we ignoring errors from commit */
}
/*! Handle an incoming validate message from a client.
* @param[in] h Clicon handle
* @param[in] db Database name
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client (eg invalid)
* @retval -1 (Local) Error
*/
int
from_client_validate(clicon_handle h,
char *db,
cbuf *cbret)
{
int retval = -1;
transaction_data_t *td = NULL;
if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>");
goto ok;
}
clicon_debug(1, "Validate %s", db);
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with commit) */
if (validate_common(h, candidate, td) < 0)
goto done;
retval = 0;
done:
/* In case of failure, call plugin transaction termination callbacks */
if (validate_common(h, db, td) < 0){
clicon_debug(1, "Validate %s failed", db);
/* XXX: candidate_validate should have proper error handling */
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-attribute</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"</rpc-error></rpc-reply>",
clicon_err_reason);
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
done:
if (retval < 0 && td)
plugin_transaction_abort(h, td);
if (td)
transaction_free(td);
return retval;
}
/*! Handle an incoming commit message from a client.
* XXX: If commit succeeds and snapshot/startup fails, we have strange state:
* the commit has succeeded but an error message is returned.
*/
int
from_client_commit(clicon_handle h,
int s,
struct clicon_msg *msg,
const char *label)
{
int retval = -1;
char *candidate;
char *running;
if (clicon_msg_commit_decode(msg,
&candidate,
&running,
label) < 0)
goto err;
if (strcmp(candidate, "candidate") && strcmp(candidate, "tmp")){
clicon_err(OE_PLUGIN, 0, "candidate is not \"candidate\" or tmp");
goto err;
}
if (strcmp(running, "running")){
clicon_err(OE_PLUGIN, 0, "running db is not \"running\"");
goto err;
}
if (candidate_commit(h, "candidate") < 0){
clicon_debug(1, "Commit %s failed", candidate);
retval = 0; /* We ignore errors from commit, but maybe
we should fail on fatal errors? */
goto err;
}
clicon_debug(1, "Commit %s", candidate);
retval = 0;
if (send_msg_ok(s) < 0)
goto done;
goto done;
err:
/* XXX: more elaborate errstring? */
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
retval = -1;
done:
unchunk_group(__FUNCTION__);
return retval; /* may be zero if we ignoring errors from commit */
} /* from_client_commit */
/*! Handle an incoming validate message from a client.
*/
int
from_client_validate(clicon_handle h,
int s,
struct clicon_msg *msg,
const char *label)
{
int retval = -1;
char *candidate;
if (clicon_msg_validate_decode(msg,
&candidate,
label) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto err;
}
if (strcmp(candidate, "candidate") != 0 && strcmp(candidate, "tmp") != 0){
clicon_err(OE_PLUGIN, 0, "candidate is not \"candidate\" or tmp");
goto err;
}
clicon_debug(1, "Validate %s", candidate);
if (candidate_validate(h, candidate) < 0){
clicon_debug(1, "Validate %s failed", candidate);
retval = 0; /* We ignore errors from commit, but maybe
we should fail on fatal errors? */
goto err;
}
retval = 0;
if (send_msg_ok(s) < 0)
goto done;
goto done;
err:
/* XXX: more elaborate errstring? */
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
retval = -1;
done:
unchunk_group(__FUNCTION__);
return retval;
} /* from_client_validate */

View file

@ -40,8 +40,9 @@
/*
* Prototypes
*/
int from_client_validate(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
int from_client_commit(clicon_handle h, int s, struct clicon_msg *msg, const char *label);
int candidate_commit(clicon_handle h, char *candidate);
int from_client_validate(clicon_handle h, char *db, cbuf *cbret);
int from_client_commit(clicon_handle h, int pid, cbuf *cbret);
int from_client_discard_changes(clicon_handle h, int pid, cbuf *cbret);
int candidate_commit(clicon_handle h, char *db);
#endif /* _BACKEND_COMMIT_H_ */

View file

@ -37,7 +37,6 @@
#ifndef _BACKEND_HANDLE_H_
#define _BACKEND_HANDLE_H_
/*
* Prototypes
* not exported.

View file

@ -57,6 +57,7 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <libgen.h>
/* cligen */
#include <cligen/cligen.h>
@ -72,7 +73,7 @@
#include "backend_handle.h"
/* Command line options to be passed to getopt(3) */
#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc:rg:ptx:"
#define BACKEND_OPTS "hD:f:d:Fzu:P:1IRCc:rg:pty:"
/*! Terminate. Cannot use h after this */
static int
@ -142,7 +143,7 @@ usage(char *argv0, clicon_handle h)
" -p \t\tPrint database yang specification\n"
" -t \t\tPrint alternate spec translation (eg if YANG print KEY, if KEY print YANG)\n"
" -g <group>\tClient membership required to this group (default: %s)\n"
" -x <status>\tSet CLICON_XMLDB_RPC to 0 or 1.\n",
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n",
argv0,
plgdir ? plgdir : "none",
confsock ? confsock : "none",
@ -153,11 +154,12 @@ usage(char *argv0, clicon_handle h)
}
static int
rundb_init(clicon_handle h)
db_reset(clicon_handle h,
char *db)
{
if (xmldb_delete(h, "running") != 0 && errno != ENOENT)
if (xmldb_delete(h, db) != 0 && errno != ENOENT)
return -1;
if (xmldb_init(h, "running") < 0)
if (xmldb_init(h, db) < 0)
return -1;
return 0;
}
@ -192,7 +194,7 @@ rundb_main(clicon_handle h,
if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL)
if (xmldb_put(h, "tmp", xn, OP_MERGE) < 0)
if (xmldb_put(h, "tmp", OP_MERGE, NULL, xn) < 0)
goto done;
if (candidate_commit(h, "tmp") < 0)
goto done;
@ -426,14 +428,15 @@ main(int argc, char **argv)
case 't' : /* Print alternative dbspec format (eg if YANG, print KEY) */
printalt++;
break;
case 'x' : /* set xmldb rpc on */
{
int i;
if (sscanf(optarg, "%d", &i) != 1)
usage(argv[0], h);
clicon_option_int_set(h, "CLICON_XMLDB_RPC", i);
}
case 'y' :{ /* yang module */
/* Set revision to NULL, extract dir and module */
char *str = strdup(optarg);
char *dir = dirname(str);
hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION");
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg));
clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir));
break;
}
default:
usage(argv[0], h);
break;
@ -503,7 +506,7 @@ main(int argc, char **argv)
if (yang_spec_main(h, stdout, printspec) < 0)
goto done;
/* First check for starup config
/* First check for startup config
XXX the options below have become out-of-hand.
Too complex, need to simplify*/
if (clicon_option_int(h, "CLICON_USE_STARTUP_CONFIG") > 0){
@ -513,8 +516,12 @@ main(int argc, char **argv)
goto done;
}
else
if (rundb_init(h) < 0)
if (db_reset(h, "running") < 0)
goto done;
if (xmldb_init(h, "candidate") < 0)
goto done;
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
}
/* If running exists and reload_running set, make a copy to candidate */
if (reload_running){
@ -529,9 +536,17 @@ main(int argc, char **argv)
/* Init running db
* -I or if it isnt there
*/
if (init_rundb || xmldb_exists(h, "running") != 1)
if (rundb_init(h) < 0)
if (init_rundb || xmldb_exists(h, "running") != 1){
if (db_reset(h, "running") < 0)
goto done;
}
/* If candidate does not exist, create it from running */
if (xmldb_exists(h, "candidate") != 1){
if (xmldb_init(h, "candidate") < 0)
goto done;
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
}
/* Initialize plugins
(also calls plugin_init() and plugin_start(argc,argv) in each plugin */

View file

@ -470,55 +470,6 @@ plugin_finish(clicon_handle h)
return 0;
}
/*! Call from frontend to function 'func' in plugin 'plugin'.
* Plugin function is supposed to populate 'retlen' and 'retarg' where
* 'retarg' is malloc:ed data if non-NULL.
* @param[in] h Clicon handle
* @param[in] req Clicon message containing information about the downcall
* @param[out] retlen Length of return value
* @param[out] ret Return value
* @retval 0 OK
* @retval -1 Error
*/
int
plugin_downcall(clicon_handle h,
struct clicon_msg_call_req *req,
uint16_t *retlen,
void **retarg)
{
int retval = -1;
int i;
downcall_cb funcp;
char name[PATH_MAX];
char *error;
struct plugin *p;
for (i = 0; i < nplugins; i++) {
p = &plugins[i];
strncpy(name, p->p_name, sizeof(name)-1);
if (!strcmp(name+strlen(name)-3, ".so"))
name[strlen(name)-3] = '\0';
/* If no plugin is given or the plugin-name matches */
if (req->cr_plugin == NULL || strlen(req->cr_plugin)==0 ||
strcmp(name, req->cr_plugin) == 0) {
funcp = dlsym(p->p_handle, req->cr_func);
if ((error = (char*)dlerror()) != NULL) {
clicon_err(OE_PROTO, ENOENT,
"Function does not exist: %s()", req->cr_func);
return -1;
}
retval = funcp(h, req->cr_op, req->cr_arglen, req->cr_arg, retlen, retarg);
goto done;
}
}
clicon_err(OE_PROTO, ENOENT,"%s: %s(): Plugin does not exist: %s",
__FUNCTION__, req->cr_func, req->cr_plugin);
return -1;
done:
return retval;
}
/*! Create and initialize transaction */
transaction_data_t *
transaction_new(void)
@ -771,4 +722,3 @@ plugin_transaction_abort(clicon_handle h,
return retval;
}

View file

@ -40,7 +40,6 @@
* Types
*/
/*! Transaction data
* Clicon internal, presented as void* to app's callback in the 'transaction_data'
* type in clicon_backend_api.h
@ -69,8 +68,6 @@ int plugin_finish(clicon_handle h);
int plugin_reset_state(clicon_handle h, char *dbname);
int plugin_start_hooks(clicon_handle h, int argc, char **argv);
int plugin_downcall(clicon_handle h, struct clicon_msg_call_req *req,
uint16_t *retlen, void **retarg);
transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *);

View file

@ -193,12 +193,12 @@ config_socket_init(clicon_handle h)
return 0;
}
/*
* config_accept_client
/*! Accept new socket client
* XXX: credentials not properly implemented
*/
int
config_accept_client(int fd, void *arg)
config_accept_client(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
@ -218,7 +218,7 @@ config_accept_client(int fd, void *arg)
char *mem;
int i;
clicon_debug(1, "%s", __FUNCTION__);
clicon_debug(2, "%s", __FUNCTION__);
len = sizeof(from);
if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){
clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__);
@ -265,7 +265,7 @@ config_accept_client(int fd, void *arg)
/*
* Here we register callbacks for actual data socket
*/
if (event_reg_fd(s, from_client, (void*)ce, "client socket") < 0)
if (event_reg_fd(s, from_client, (void*)ce, "local netconf client socket") < 0)
goto done;
retval = 0;
done:

View file

@ -133,6 +133,7 @@ backend_notify(clicon_handle h,
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;
@ -161,7 +162,7 @@ backend_notify(clicon_handle h,
/* Then go thru all global (handle) subscriptions and find matches */
hs = NULL;
while ((hs = subscription_each(h, hs)) != NULL){
if (hs->hs_format != MSG_NOTIFY_TXT)
if (hs->hs_format != FORMAT_TEXT)
continue;
if (strcmp(hs->hs_stream, stream))
continue;
@ -239,7 +240,7 @@ backend_notify_xml(clicon_handle h,
/* Then go thru all global (handle) subscriptions and find matches */
hs = NULL;
while ((hs = subscription_each(h, hs)) != NULL){
if (hs->hs_format != MSG_NOTIFY_XML)
if (hs->hs_format != FORMAT_XML)
continue;
if (strcmp(hs->hs_stream, stream))
continue;
@ -257,7 +258,8 @@ backend_notify_xml(clicon_handle h,
}
struct client_entry *
backend_client_add(clicon_handle h, struct sockaddr *addr)
backend_client_add(clicon_handle h,
struct sockaddr *addr)
{
struct backend_handle *cb = handle(h);
struct client_entry *ce;
@ -411,3 +413,79 @@ subscription_each(clicon_handle h,
hs = cb->cb_subscription;
return hs;
}
/* Database dependency description */
struct backend_netconf_reg {
qelem_t nr_qelem; /* List header */
backend_netconf_cb_t nr_callback; /* Validation/Commit Callback */
void *nr_arg; /* Application specific argument to cb */
char *nr_tag; /* Xml tag when matched, callback called */
};
typedef struct backend_netconf_reg backend_netconf_reg_t;
static backend_netconf_reg_t *deps = NULL;
/*! Register netconf callback
* Called from plugin to register a callback for a specific netconf XML tag.
*/
int
backend_netconf_register_callback(clicon_handle h,
backend_netconf_cb_t cb, /* Callback called */
void *arg, /* Arg to send to callback */
char *tag) /* Xml tag when callback is made */
{
backend_netconf_reg_t *nr;
if ((nr = malloc(sizeof(backend_netconf_reg_t))) == NULL) {
clicon_err(OE_DB, errno, "malloc: %s", strerror(errno));
goto catch;
}
memset (nr, 0, sizeof (*nr));
nr->nr_callback = cb;
nr->nr_arg = arg;
nr->nr_tag = strdup(tag); /* XXX strdup memleak */
INSQ(nr, deps);
return 0;
catch:
if (nr){
if (nr->nr_tag)
free(nr->nr_tag);
free(nr);
}
return -1;
}
/*! See if there is any callback registered for this tag
*
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[out] cb Output xml stream. For reply
* @param[out] cb_err Error xml stream. For error reply
* @param[out] xret Return XML, error or OK
*
* @retval -1 Error
* @retval 0 OK, not found handler.
* @retval 1 OK, handler called
*/
int
backend_netconf_plugin_callbacks(clicon_handle h,
cxobj *xe,
struct client_entry *ce,
cbuf *cbret)
{
backend_netconf_reg_t *nreg;
int retval;
if (deps == NULL)
return 0;
nreg = deps;
do {
if (strcmp(nreg->nr_tag, xml_name(xe)) == 0){
if ((retval = nreg->nr_callback(h, xe, ce, cbret, nreg->nr_arg)) < 0)
return -1;
else
return 1; /* handled */
}
nreg = NEXTQ(backend_netconf_reg_t *, nreg);
} while (nreg != deps);
return 0;
}

View file

@ -43,6 +43,14 @@
/*
* Types
*/
struct client_entry;
typedef int (*backend_netconf_cb_t)(
clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg /* Argument given at register */
);
/*! Generic downcall registration.
* Enables any function to be called from (cli) frontend
@ -81,4 +89,13 @@ int subscription_delete(clicon_handle h, char *stream,
struct handle_subscription *subscription_each(clicon_handle h,
struct handle_subscription *hprev);
int backend_netconf_register_callback(clicon_handle h,
backend_netconf_cb_t cb, /* Callback called */
void *arg, /* Arg to send to callback */
char *tag); /* Xml tag when callback is made */
int backend_netconf_plugin_callbacks(clicon_handle h, cxobj *xe,
struct client_entry *ce, cbuf *cbret);
#endif /* _CLIXON_BACKEND_HANDLE_H_ */

File diff suppressed because it is too large Load diff

View file

@ -65,10 +65,9 @@
/* This is the default callback function. But this is typically overwritten */
#define GENERATE_CALLBACK "cli_set"
#define GENERATE_CALLBACKV "cli_setv"
/* variable expand function */
#define GENERATE_EXPAND_XMLDB "expandv_dbvar"
#define GENERATE_EXPAND_XMLDB "expand_dbvar"
/*=====================================================================
* YANG generate CLI
@ -154,10 +153,7 @@ cli_callback_generate(clicon_handle h,
if (yang2xmlkeyfmt(ys, 0, &xkfmt) < 0)
goto done;
if (clicon_option_int(h, "CLICON_CLIGEN_CALLBACK_SINGLE_ARG")==1)
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
else
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACKV, xkfmt);
cprintf(cb0, ",%s(\"%s\")", GENERATE_CALLBACK, xkfmt);
retval = 0;
done:
if (xkfmt)

View file

@ -54,6 +54,7 @@
#include <sys/param.h>
#include <netinet/in.h>
#include <assert.h>
#include <libgen.h>
/* cligen */
#include <cligen/cligen.h>
@ -69,7 +70,7 @@
#include "cli_handle.h"
/* Command line options to be passed to getopt(3) */
#define CLI_OPTS "hD:f:F:1u:d:m:qpGLl:"
#define CLI_OPTS "hD:f:F:1u:d:m:qpGLl:y:"
/*! terminate cli application */
static int
@ -77,10 +78,10 @@ cli_terminate(clicon_handle h)
{
yang_spec *yspec;
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
cli_plugin_finish(h);
exit_candidate_db(h);
cli_handle_exit(h);
return 0;
}
@ -150,7 +151,8 @@ usage(char *argv0, clicon_handle h)
"\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> \tLog on (s)yslog, std(e)rr or std(o)ut (stderr is default)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n",
argv0,
confsock ? confsock : "none",
plgdir ? plgdir : "none"
@ -174,7 +176,7 @@ main(int argc, char **argv)
int help = 0;
char *treename;
int logdst = CLICON_LOG_STDERR;
char *restarg; /* what remains after options */
char *restarg = NULL; /* what remains after options */
/* Defaults */
@ -288,6 +290,15 @@ main(int argc, char **argv)
case 'L' : /* Debug print dynamic CLI syntax */
logclisyntax++;
break;
case 'y' :{ /* yang module */
/* Set revision to NULL, extract dir and module */
char *str = strdup(optarg);
char *dir = dirname(str);
hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION");
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg));
clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir));
break;
}
default:
usage(argv[0], h);
break;
@ -359,23 +370,14 @@ main(int argc, char **argv)
goto done;
}
/* A client does not have access to the candidate (and running)
databases if both these conditions are true:
1. clicon_sock_family(h) == AF_INET[6]
*/
if (clicon_sock_family(h) == AF_UNIX)
if (init_candidate_db(h) < 0)
return -1;
if (logclisyntax)
cli_logsyntax_set(h, logclisyntax);
if (debug)
clicon_option_dump(h, debug);
/* Join rest of argv to a single command */
restarg = clicon_strjoin(argc, argv, " ", __FUNCTION__);
restarg = clicon_strjoin(argc, argv, " ");
/* If several cligen object variables match same preference, select first */
cligen_match_cgvar_same(1);
@ -398,6 +400,8 @@ main(int argc, char **argv)
if (!once)
cli_interactive(h);
done:
if (restarg)
free(restarg);
unchunk_group(__FUNCTION__);
// Gets in your face if we log on stderr
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */

View file

@ -372,35 +372,21 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
}
/* Resolve callback names to function pointers. */
if (clicon_option_int(h, "CLICON_CLIGEN_CALLBACK_SINGLE_ARG")==1){
if (cligen_callback_str2fn(pt, (cg_str2fn_t*)clixon_str2fn, handle) < 0){
clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)",
filename, plgnam, plgnam);
goto done;
}
}
else
if (cligen_callbackv_str2fn(pt, (cgv_str2fn_t*)clixon_str2fn, handle) < 0){
clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)",
filename, plgnam, plgnam);
goto done;
}
if (clicon_option_int(h, "CLICON_CLIGEN_EXPAND_SINGLE_ARG")==1){
if (cligen_expand_str2fn(pt, (expand_str2fn_t*)clixon_str2fn, handle) < 0)
goto done;
if (cligen_callbackv_str2fn(pt, (cgv_str2fn_t*)clixon_str2fn, handle) < 0){
clicon_err(OE_PLUGIN, 0, "Mismatch between CLIgen file '%s' and CLI plugin file '%s'. Some possible errors:\n\t1. A function given in the CLIgen file does not exist in the plugin (ie link error)\n\t2. The CLIgen spec does not point to the correct plugin .so file (CLICON_PLUGIN=\"%s\" is wrong)",
filename, plgnam, plgnam);
goto done;
}
else
if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, handle) < 0)
goto done;
if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, handle) < 0)
goto done;
/* Make sure we have a syntax mode specified */
if (mode == NULL || strlen(mode) < 1) { /* may be null if not given in file */
clicon_err(OE_PLUGIN, 0, "No syntax mode specified in %s", filepath);
goto done;
}
if ((vec = clicon_strsplit(mode, ":", &nvec, __FUNCTION__)) == NULL) {
if ((vec = clicon_strsep(mode, ":", &nvec)) == NULL)
goto done;
}
for (i = 0; i < nvec; i++) {
if (syntax_append(h, cli_syntax(h), vec[i], pt) < 0) {
goto done;
@ -415,6 +401,8 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
done:
if (vr)
cvec_free(vr);
if (vec)
free(vec);
unchunk_group(__FUNCTION__);
return retval;
}
@ -1020,7 +1008,7 @@ cli_ptpush(clicon_handle h, char *mode, char *string, char *op)
return 0;
pt = &co_cmd->co_pt;
/* vec is the command, eg 'edit policy_option' */
if ((vec = clicon_strsplit(string, " ", &nvec, __FUNCTION__)) == NULL)
if ((vec = clicon_strsep(string, " ", &nvec)) == NULL)
goto catch;
co = NULL;
found = 0;
@ -1050,7 +1038,8 @@ cli_ptpush(clicon_handle h, char *mode, char *string, char *op)
co_up_set(cc, co_cmd);
}
catch:
unchunk_group(__FUNCTION__) ;
if (vec)
free(vec);
return 0;
}

File diff suppressed because it is too large Load diff

View file

@ -68,70 +68,82 @@ int cli_handle_exit(clicon_handle h);
cligen_handle cli_cligen(clicon_handle h);
/* cli_common.c */
int init_candidate_db(clicon_handle h);
int exit_candidate_db(clicon_handle h);
int cli_notification_register(clicon_handle h, char *stream, enum format_enum format,
char *filter, int status,
int (*fn)(int, void*), void *arg);
#define cli_output cligen_output
/* cli_common.c: CLIgen new vector callbacks */
int cli_set(clicon_handle h, cvec *vars, cvec *argv);
int cli_setv(clicon_handle h, cvec *vars, cvec *argv);
int cli_merge(clicon_handle h, cvec *vars, cvec *argv);
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv);
int cli_del(clicon_handle h, cvec *vars, cvec *argv);
int cli_delv(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_cli(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_backend(clicon_handle h, cvec *vars, cvec *argv);
int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv);
int cli_set_mode(clicon_handle h, cvec *vars, cvec *argv);
int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv);
int cli_start_shell(clicon_handle h, cvec *vars, cvec *argv);
int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv);
int cli_quit(clicon_handle h, cvec *vars, cvec *argv);
int cli_quitv(clicon_handle h, cvec *vars, cvec *argv);
int cli_commit(clicon_handle h, cvec *vars, cvec *argv);
int cli_commitv(clicon_handle h, cvec *vars, cvec *argv);
int cli_validate(clicon_handle h, cvec *vars, cvec *argv);
int cli_validatev(clicon_handle h, cvec *vars, cvec *argv);
int compare_dbs(clicon_handle h, cvec *vars, cvec *argv);
int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv);
int load_config_file(clicon_handle h, cvec *vars, cvec *argv);
int load_config_filev(clicon_handle h, cvec *vars, cvec *argv);
int save_config_file(clicon_handle h, cvec *vars, cvec *argv);
int save_config_filev(clicon_handle h, cvec *vars, cvec *argv);
int delete_all(clicon_handle h, cvec *vars, cvec *argv);
int delete_allv(clicon_handle h, cvec *vars, cvec *argv);
int discard_changes(clicon_handle h, cvec *vars, cvec *argv);
int discard_changesv(clicon_handle h, cvec *vars, cvec *argv);
int cli_notify(clicon_handle h, cvec *cvv, cvec *argv);
int cli_notifyv(clicon_handle h, cvec *cvv, cvec *argv);
/* cli_common.c: CLIgen old single arg callbacks */
int cli_set(clicon_handle h, cvec *vars, cg_var *arg);
int cli_merge(clicon_handle h, cvec *vars, cg_var *arg);
int cli_del(clicon_handle h, cvec *vars, cg_var *arg);
int cli_debug_cli(clicon_handle h, cvec *vars, cg_var *arg);
int cli_debug_backend(clicon_handle h, cvec *vars, cg_var *argv);
int cli_set_mode(clicon_handle h, cvec *vars, cg_var *argv);
int cli_start_shell(clicon_handle h, cvec *vars, cg_var *argv);
int cli_quit(clicon_handle h, cvec *vars, cg_var *arg);
int cli_commit(clicon_handle h, cvec *vars, cg_var *arg);
int cli_validate(clicon_handle h, cvec *vars, cg_var *arg);
int compare_dbs(clicon_handle h, cvec *vars, cg_var *arg);
int load_config_file(clicon_handle h, cvec *vars, cg_var *arg);
int save_config_file(clicon_handle h, cvec *vars, cg_var *arg);
int delete_all(clicon_handle h, cvec *vars, cg_var *arg);
int discard_changes(clicon_handle h, cvec *vars, cg_var *arg);
int cli_notify(clicon_handle h, cvec *cvv, cg_var *arg);
int db_copy(clicon_handle h, cvec *cvv, cvec *argv);
int cli_lock(clicon_handle h, cvec *cvv, cvec *argv);
int cli_unlock(clicon_handle h, cvec *cvv, cvec *argv);
int cli_copy_config(clicon_handle h, cvec *cvv, cvec *argv);
/* In cli_show.c */
int expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail);
int expand_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
cvec *commands, cvec *helptexts);
int expandv_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
cvec *commands, cvec *helptexts);
/* cli_show.c: CLIgen new vector arg callbacks */
int show_confv_as_xml(clicon_handle h, cvec *vars, cvec *argv);
int show_confv_as_netconf(clicon_handle h, cvec *vars, cvec *argv);
int show_confv_as_json(clicon_handle h, cvec *vars, cvec *argv);
int show_confv_as_text(clicon_handle h, cvec *vars, cvec *argv);
int show_confv_as_cli(clicon_handle h, cvec *vars, cvec *argv);
int show_confv_as_csv(clicon_handle h, cvec *vars, cvec *argv);
int show_yang(clicon_handle h, cvec *vars, cvec *argv);
int show_yangv(clicon_handle h, cvec *vars, cvec *argv);
int show_conf_xpath(clicon_handle h, cvec *cvv, cvec *argv);
int show_confv_xpath(clicon_handle h, cvec *cvv, cvec *argv);
/* cli_show.c: CLIgen old single arg callbacks */
int show_conf_as_xml(clicon_handle h, cvec *vars, cg_var *arg);
int show_conf_as_netconf(clicon_handle h, cvec *vars, cg_var *arg);
int show_conf_as_json(clicon_handle h, cvec *vars, cg_var *arg);
int show_conf_as_text(clicon_handle h, cvec *vars, cg_var *arg);
int show_conf_as_cli(clicon_handle h, cvec *vars, cg_var *arg);
int show_conf_as_csv(clicon_handle h, cvec *vars, cg_var *arg);
int show_yang(clicon_handle h, cvec *vars, cg_var *arg);
int cli_show_config(clicon_handle h, cvec *cvv, cvec *argv);
#endif /* _CLIXON_CLI_API_H_ */

View file

@ -209,21 +209,28 @@ main(int argc, char **argv)
}
if (dumpdb){
/* Here db must be local file-path */
if (xmldb_dump_local(stdout, db, matchkey)) {
if (xmldb_dump(stdout, db, matchkey)) {
fprintf(stderr, "Match error\n");
goto done;
}
}
if (addent) /* add entry */
if (xmldb_put_xkey(h, db, addstr, NULL, OP_REPLACE) < 0)
if (addent){ /* add entry */
cxobj *xml = NULL;
if (clicon_xml_parse(&xml, "<config>%s</config>", addstr) < 0)
goto done;
if (xmldb_put(h, db, OP_REPLACE, NULL, xml) < 0)
goto done;
if (xml)
xml_free(xml);
}
if (rment)
if (remove_entry(db, rmkey) < 0)
goto done;
if (zapdb) /* remove databases */
/* XXX This assumes direct access to database */
if (xmldb_delete(h, db) < 0){
clicon_err(OE_FATAL, errno, "xmldb_delete %s", db);
clicon_err(OE_FATAL, errno, "delete %s", db);
goto done;
}
if (initdb)

View file

@ -44,11 +44,9 @@
*/
typedef int (*netconf_cb_t)(
clicon_handle h,
cxobj *xorig, /* Original request. */
cxobj *xn, /* Sub-tree (under xorig) at child: <rpc><xn></rpc> */
cbuf *cb, /* Output xml stream. For reply */
cbuf *cb_err, /* Error xml stream. For error reply */
void *arg /* Argument given at netconf_register_callback() */
cxobj *xn, /* Request: <rpc><xn></rpc> */
cxobj **xret, /* Return xml tree, eg <rpc-reply>... */
void *arg /* Argument given at netconf_register_callback() */
);
/*
@ -57,25 +55,10 @@ typedef int (*netconf_cb_t)(
*/
int netconf_output(int s, cbuf *xf, char *msg);
int netconf_create_rpc_reply(cbuf *cb, /* msg buffer */
cxobj *xr, /* orig request */
char *body,
int ok);
int netconf_register_callback(clicon_handle h,
netconf_cb_t cb, /* Callback called */
void *arg, /* Arg to send to callback */
char *tag); /* Xml tag when callback is made */
int netconf_create_rpc_error(cbuf *xf, /* msg buffer */
cxobj *xr, /* orig request */
char *tag,
char *type,
char *severity,
char *message,
char *info);
void netconf_ok_set(int ok);
int netconf_ok_get(void);
int netconf_xpath(cxobj *xsearch,
cxobj *xfilter,

View file

@ -65,9 +65,7 @@
#include "netconf_lib.h"
#include "netconf_filter.h"
/*
* xml_filter
* xf specifices a filter, and xn is an xml tree.
/* xf specifices a filter, and xn is an xml tree.
* Select the part of xn that matches xf and return it.
* Change xn destructively by removing the parts of the sub-tree that does
* not match.
@ -107,9 +105,9 @@ leafstring(cxobj *x)
* @retval -1 Error
*/
static int
xml_filter2(cxobj *xfilter,
cxobj *xparent,
int *remove_me)
xml_filter_recursive(cxobj *xfilter,
cxobj *xparent,
int *remove_me)
{
cxobj *s;
cxobj *sprev;
@ -176,7 +174,7 @@ xml_filter2(cxobj *xfilter,
}
// XXX: s can be removed itself in the recursive call !
remove_s = 0;
if (xml_filter2(f, s, &remove_s) < 0)
if (xml_filter_recursive(f, s, &remove_s) < 0)
return -1;
if (remove_s){
xml_purge(s);
@ -207,9 +205,9 @@ xml_filter(cxobj *xfilter,
int remove_s;
/* Call recursive variant */
retval = xml_filter2(xfilter,
xconfig,
&remove_s);
retval = xml_filter_recursive(xfilter,
xconfig,
&remove_s);
return retval;
}

View file

@ -68,22 +68,8 @@
/*
* Exported variables
*/
enum transport_type transport = NETCONF_SSH;
int cc_closed = 0;
static int cc_ok = 0;
void
netconf_ok_set(int ok)
{
cc_ok = ok;
}
int
netconf_ok_get(void)
{
return cc_ok;
}
enum transport_type transport = NETCONF_SSH; /* XXX Remove SOAP support */
int cc_closed = 0; /* XXX Please remove (or at least hide in handle) this global variable */
int
add_preamble(cbuf *xf)
@ -161,38 +147,13 @@ add_error_postamble(cbuf *xf)
return 0;
}
/*! 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
* @param[in,out] state A state integer holding how far we have parsed.
* @retval 0 No, we havent detected end tag
* @retval 1 Yes, we have detected end tag!
* XXX: move to clicon_xml?
*/
int
detect_endtag(char *tag, char ch, int *state)
{
int retval = 0;
if (tag[*state] == ch){
(*state)++;
if (*state == strlen(tag)){
*state = 0;
retval = 1;
}
}
else
*state = 0;
return retval;
}
/*! Get "target" attribute, return actual database given candidate or running
* Caller must do error handling
* @retval dbname Actual database file name
*/
char *
netconf_get_target(clicon_handle h,
cxobj *xn,
netconf_get_target(cxobj *xn,
char *path)
{
cxobj *x;
@ -218,11 +179,13 @@ netconf_get_target(clicon_handle h,
* @param[in] msg Only for debug
*/
int
netconf_output(int s, cbuf *xf, char *msg)
netconf_output(int s,
cbuf *xf,
char *msg)
{
char *buf = cbuf_get(xf);
int len = cbuf_len(xf);
int retval = -1;
int len = cbuf_len(xf);
int retval = -1;
clicon_debug(1, "SEND %s", msg);
if (debug > 1){ /* XXX: below only works to stderr, clicon_debug may log to syslog */

View file

@ -60,11 +60,6 @@ enum error_option{ /* edit-config */
CONTINUE_ON_ERROR
};
enum filter_option{ /* get-config/filter */
FILTER_SUBTREE,
FILTER_XPATH
};
/*
* Variables
*/
@ -74,14 +69,10 @@ extern int cc_closed;
/*
* Prototypes
*/
void netconf_ok_set(int ok);
int netconf_ok_get(void);
int add_preamble(cbuf *xf);
int add_postamble(cbuf *xf);
int add_error_preamble(cbuf *xf, char *reason);
int detect_endtag(char *tag, char ch, int *state);
char *netconf_get_target(clicon_handle h, cxobj *xn, char *path);
char *netconf_get_target(cxobj *xn, char *path);
int add_error_postamble(cbuf *xf);
int netconf_output(int s, cbuf *xf, char *msg);

View file

@ -56,6 +56,7 @@
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
#include <libgen.h>
/* cligen */
#include <cligen/cligen.h>
@ -70,7 +71,7 @@
#include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */
#define NETCONF_OPTS "hDqf:d:S"
#define NETCONF_OPTS "hDqf:d:Sy:"
/*! Process incoming packet
* @param[in] h Clicon handle
@ -82,11 +83,12 @@ process_incoming_packet(clicon_handle h,
{
char *str;
char *str0;
cxobj *xml_req = NULL; /* Request (in) */
cxobj *xreq = NULL; /* Request (in) */
int isrpc = 0; /* either hello or rpc */
cbuf *xf_out;
cbuf *xf_err;
cbuf *xf1;
cbuf *cbret = NULL;
cxobj *xret = NULL; /* Return (out) */
cxobj *xrpc;
cxobj *xc;
clicon_debug(1, "RECV");
clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb));
@ -96,16 +98,15 @@ process_incoming_packet(clicon_handle h,
}
str = str0;
/* Parse incoming XML message */
if (clicon_xml_parse_string(&str, &xml_req) < 0){
if ((cb = cbuf_new()) == NULL){
netconf_create_rpc_error(cb, NULL,
"operation-failed",
"rpc", "error",
NULL,
NULL);
if (clicon_xml_parse_string(&str, &xreq) < 0){
if ((cbret = cbuf_new()) == NULL){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>rpc</error-type>"
"<error-severity>error</error-severity>"
"<error-message>internal error</error-message>"
"</rpc-error></rpc-reply>");
netconf_output(1, cb, "rpc-error");
cbuf_free(cb);
}
else
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
@ -113,72 +114,56 @@ process_incoming_packet(clicon_handle h,
goto done;
}
free(str0);
if (xpath_first(xml_req, "//rpc") != NULL){
if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){
isrpc++;
}
else
if (xpath_first(xml_req, "//hello") != NULL)
if (xpath_first(xreq, "//hello") != NULL)
;
else{
clicon_log(LOG_WARNING, "Invalid netconf msg: neither rpc or hello: dropp\
ed");
goto done;
}
/* Initialize response buffers */
if ((xf_out = cbuf_new()) == NULL){
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
goto done;
if (!isrpc){ /* hello */
if (netconf_hello_dispatch(xreq) < 0)
goto done;
}
else /* rpc */
if (netconf_rpc_dispatch(h, xrpc, &xret) < 0){
goto done;
}
else{ /* there is a return message in xret */
cxobj *xa, *xa2;
assert(xret);
/* Create error buf */
if ((xf_err = cbuf_new()) == NULL){
clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__);
goto done;
}
netconf_ok_set(0);
if (isrpc){
if (netconf_rpc_dispatch(h,
xml_req,
xpath_first(xml_req, "//rpc"),
xf_out, xf_err) < 0){
assert(cbuf_len(xf_err));
clicon_debug(1, "%s", cbuf_get(xf_err));
if (isrpc){
if (netconf_output(1, xf_err, "rpc-error") < 0)
goto done;
if ((cbret = cbuf_new()) != NULL){
if ((xc = xml_child_i(xret,0))!=NULL){
xa=NULL;
while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){
if ((xa2 = xml_dup(xa)) ==NULL)
goto done;
if (xml_addsub(xc, xa2) < 0)
goto done;
}
add_preamble(cbret);
clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0);
add_postamble(cbret);
if (netconf_output(1, cbret, "rpc-reply") < 0){
cbuf_free(cbret);
goto done;
}
}
}
}
else{
if ((xf1 = cbuf_new()) != NULL){
if (netconf_create_rpc_reply(xf1, xml_req, cbuf_get(xf_out), netconf_ok_get()) < 0){
cbuf_free(xf_out);
cbuf_free(xf_err);
cbuf_free(xf1);
goto done;
}
if (netconf_output(1, xf1, "rpc-reply") < 0){
cbuf_reset(xf1);
netconf_create_rpc_error(xf1, xml_req, "operation-failed",
"protocol", "error",
NULL, cbuf_get(xf_err));
netconf_output(1, xf1, "rpc-error");
cbuf_free(xf_out);
cbuf_free(xf_err);
cbuf_free(xf1);
goto done;
}
cbuf_free(xf1);
}
}
}
else{
netconf_hello_dispatch(xml_req); /* XXX: return-value */
}
cbuf_free(xf_out);
cbuf_free(xf_err);
done:
if (xml_req)
xml_free(xml_req);
if (xreq)
xml_free(xreq);
if (xret)
xml_free(xret);
if (cbret)
cbuf_free(cbret);
return 0;
}
@ -190,19 +175,18 @@ static int
netconf_input_cb(int s,
void *arg)
{
int retval = -1;
clicon_handle h = arg;
unsigned char buf[BUFSIZ];
int i;
int len;
static cbuf *cb; /* XXX: should use ce state? */
cbuf *cb=NULL;
int xml_state = 0;
int retval = -1;
if (cb == NULL)
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__);
return retval;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "%s: cbuf_new", __FUNCTION__);
return retval;
}
memset(buf, 0, sizeof(buf));
if ((len = read(s, buf, sizeof(buf))) < 0){
if (errno == ECONNRESET)
@ -237,7 +221,8 @@ netconf_input_cb(int s,
}
retval = 0;
done:
// cbuf_free(cb);
if (cb)
cbuf_free(cb);
if (cc_closed)
retval = -1;
return retval;
@ -268,30 +253,16 @@ send_hello(int s)
return retval;
}
/*! Initialize candidate database */
static int
init_candidate_db(clicon_handle h)
{
int retval = -1;
/* init shared candidate */
if (xmldb_exists(h, "candidate") != 1){
if (xmldb_copy(h, "running", "candidate") < 0)
goto done;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
static int
terminate(clicon_handle h)
netconf_terminate(clicon_handle h)
{
yang_spec *yspec;
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec);
event_exit();
clicon_handle_exit(h);
return 0;
}
@ -314,7 +285,8 @@ usage(clicon_handle h,
"\t-q\t\tQuiet: dont send hello prompt\n"
"\t-f <file>\tConfiguration file (mandatory)\n"
"\t-d <dir>\tSpecify netconf plugin directory dir (default: %s)\n"
"\t-S\t\tLog on syslog\n",
"\t-S\t\tLog on syslog\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n",
argv0,
netconfdir
);
@ -387,6 +359,15 @@ main(int argc, char **argv)
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_NETCONF_DIR", optarg);
break;
case 'y' :{ /* yang module */
/* Set revision to NULL, extract dir and module */
char *str = strdup(optarg);
char *dir = dirname(str);
hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION");
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(optarg));
clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir));
break;
}
default:
usage(h, argv[0]);
break;
@ -402,8 +383,6 @@ main(int argc, char **argv)
if (netconf_plugin_load(h) < 0)
return -1;
if (init_candidate_db(h) < 0)
return -1;
/* Call start function is all plugins before we go interactive */
tmp = *(argv-1);
*(argv-1) = argv0;
@ -416,13 +395,11 @@ main(int argc, char **argv)
goto done;
if (debug)
clicon_option_dump(h, debug);
if (event_loop() < 0)
goto done;
done:
netconf_plugin_unload(h);
terminate(h);
netconf_terminate(h);
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */
clicon_log(LOG_NOTICE, "%s: %u Terminated\n", __PROGRAM__, getpid());
return 0;

View file

@ -66,6 +66,15 @@
#include "netconf_lib.h"
#include "netconf_plugin.h"
/* Database dependency description */
struct netconf_reg {
qelem_t nr_qelem; /* List header */
netconf_cb_t nr_callback; /* Validation/Commit Callback */
void *nr_arg; /* Application specific argument to cb */
char *nr_tag; /* Xml tag when matched, callback called */
};
typedef struct netconf_reg netconf_reg_t;
/*! Unload a plugin
*/
static int
@ -245,10 +254,9 @@ catch:
/*! See if there is any callback registered for this tag
*
* @param xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param xf Output xml stream. For reply
* @param xf_err Error xml stream. For error reply
* @param xorig Original request.
* @param[in] h clicon handle
* @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param[out] xret Return XML, error or OK
*
* @retval -1 Error
* @retval 0 OK, not found handler.
@ -256,31 +264,24 @@ catch:
*/
int
netconf_plugin_callbacks(clicon_handle h,
cxobj *xn,
cbuf *xf,
cbuf *xf_err,
cxobj *xorig)
cxobj *xn,
cxobj **xret)
{
netconf_reg_t *nr;
netconf_reg_t *nreg;
int retval;
if (deps == NULL)
return 0;
nr = deps;
nreg = deps;
do {
if (strcmp(nr->nr_tag, xml_name(xn)) == 0){
if ((retval = nr->nr_callback(h,
xorig,
xn,
xf,
xf_err,
nr->nr_arg)) < 0)
if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){
if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0)
return -1;
else
return 1; /* handled */
}
nr = NEXTQ(netconf_reg_t *, nr);
} while (nr != deps);
nreg = NEXTQ(netconf_reg_t *, nreg);
} while (nreg != deps);
return 0;
}

View file

@ -41,14 +41,6 @@
* Types
*/
/* Database dependency description */
struct netconf_reg {
qelem_t nr_qelem; /* List header */
netconf_cb_t nr_callback; /* Validation/Commit Callback */
void *nr_arg; /* Application specific argument to cb */
char *nr_tag; /* Xml tag when matched, callback called */
};
typedef struct netconf_reg netconf_reg_t;
/*
* Prototypes
@ -59,12 +51,6 @@ int netconf_plugin_start(clicon_handle h, int argc, char **argv);
int netconf_plugin_unload(clicon_handle h);
int netconf_plugin_callbacks(clicon_handle h,
// dbspec_key *dbspec,
cxobj *xn,
cbuf *xf,
cbuf *xf_err,
cxobj *xt);
int netconf_plugin_callbacks(clicon_handle h, cxobj *xn, cxobj **xret);
#endif /* _NETCONF_PLUGIN_H_ */

File diff suppressed because it is too large Load diff

View file

@ -42,20 +42,7 @@
*/
int
netconf_rpc_dispatch(clicon_handle h,
cxobj *xorig,
cxobj *xn,
cbuf *xf,
cbuf *xf_err);
int netconf_create_rpc_reply(cbuf *xf, /* msg buffer */
cxobj *xr, /* orig request */
char *body, int ok);
int netconf_create_rpc_error(cbuf *xf, /* msg buffer */
cxobj *xr, /* orig request */
char *tag,
char *type,
char *severity,
char *message,
char *info);
cxobj *xn,
cxobj **xret);
#endif /* _NETCONF_RPC_H_ */

View file

@ -62,6 +62,7 @@ CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
SRC = restconf_lib.c
SRC += restconf_methods.c
OBJS = $(SRC:.c=.o)

View file

@ -1,19 +1,26 @@
Clixon Restconf
===============
Contents:
1. Features
2. Installation using NGINX
3. Debugging
1. FEATURES
+++++++++++
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
run with NGINX.
The implementatation supports plain OPTIONS, HEAD, GET, POST, PUT, PATCH, DELETE.
and is based on draft-ietf-netconf-restconf-13.
There is currently (2017) a RFC 8040, many of those features are _not_ implemented,
including:
- query parameters (section 4.9)
- notifications (sec 6)
- only rudimentary error reporting exists (sec 7)
2. INSTALLATION using NGINX
+++++++++++++++++++++++++++
# Existing clixon installation. Using CLI:
olof@vandal> clixon_cli -f /usr/local/etc/routing.conf
olof@vandal> show configuration
interfaces {
interface {
name eth0;
type eth;
enabled true;
}
interface {
name eth9;
type eth;
enabled true;
}
}
# Define nginx config file/etc/nginx/sites-available/default
server {
...
@ -54,8 +61,11 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
}
]
Debugging
---------
curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}}' http://localhost/restconf/data
3. DEBUGGING
++++++++++++
Start the restconf programs with debug flag:
sudo su -c "/www-data/clixon_restconf -D" -s /bin/sh www-data

View file

@ -1,6 +1,39 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

View file

@ -1,9 +1,36 @@
/*
*
* $COPYRIGHTSTATEMENT$
*
* $LICENSE$
* This is backend headend sender code, ie communication with a pmagent
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifndef _RESTCONF_LIB_H_
@ -12,13 +39,6 @@
/*
* Constants
*/
#define USER_COOKIE "c-user" /* connected user cookie */
#define WWW_USER "root"
#define WWW_PASSWD "9rundpaj" // XXX
#define DEFAULT_TEMPLATE "nordunet" /* XXX Default sender template must be in conf */
#define CONFIG_FILE "/usr/local/etc/grideye.conf"
#define NETCONF_BIN "/usr/local/bin/clixon_netconf"
#define NETCONF_OPTS "-qS"
/*
* Prototypes

View file

@ -59,6 +59,7 @@
#include <sys/time.h>
#include <sys/wait.h>
#include <curl/curl.h>
#include <libgen.h>
/* cligen */
#include <cligen/cligen.h>
@ -66,302 +67,17 @@
/* clicon */
#include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h"
#include "restconf_methods.h"
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hDf:p:"
#define RESTCONF_OPTS "hDf:p:y:"
/* Should be discovered via "/.well-known/host-meta"
resource ([RFC6415]) */
#define RESTCONF_API_ROOT "/restconf/"
/*! REST OPTIONS method
* According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] head Set if HEAD request instead of GET
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
*/
static int
api_data_options(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int head)
{
int retval = -1;
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
FCGX_FPrintF(r->out, "GET, HEAD, OPTIONS, PUT, POST, DELETE\r\n");
retval = 0;
return retval;
}
/*! Generic REST GET method
* According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang.data+xml
* Content-Type: application/yang.data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
*/
static int
api_data_get(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
{
int retval = -1;
cg_var *cv;
char *val;
char *v;
int i;
cbuf *path = NULL;
cbuf *path1 = NULL;
cxobj *xt = NULL;
cbuf *cbx = NULL;
cxobj **vec = NULL;
size_t veclen;
yang_spec *yspec;
yang_stmt *y;
yang_stmt *ykey;
char *name;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL)
goto done;
if ((path1 = cbuf_new()) == NULL) /* without [] qualifiers */
goto done;
cv = NULL;
cprintf(path1, "/");
/* translate eg a/b=c -> a/[b=c] */
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
name = cv_name_get(cv);
clicon_debug(1, "[%d] cvname:%s", i, name);
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
if (i == pi){
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else{
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
v = val;
/* XXX sync with yang */
while((v=index(v, ',')) != NULL){
*v = '\0';
v++;
}
/* Find keys */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
notfound(r);
goto done;
}
clicon_debug(1, "ykey:%s", ykey->ys_argument);
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(path, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(path, "[%s=%s]", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
free(val);
}
else{
cprintf(path, "%s%s", (i==pi?"":"/"), name);
cprintf(path1, "/%s", name);
}
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (xmldb_get(h, "running", cbuf_get(path), &xt, &vec, &veclen) < 0)
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
if (veclen==0){
notfound(r);
goto done;
}
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n");
FCGX_FPrintF(r->out, "\r\n");
if (xml2json_cbuf_vec(cbx, vec, veclen, 0) < 0)
goto done;
FCGX_FPrintF(r->out, "%s", cbuf_get(cbx));
FCGX_FPrintF(r->out, "\r\n\r\n");
retval = 0;
done:
if (cbx)
cbuf_free(cbx);
if (xt)
xml_free(xt);
if (path)
cbuf_free(path);
if (path1)
cbuf_free(path1);
return retval;
}
/*! Generic REST DELETE method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pi Offset, where path starts
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
*/
static int
api_data_delete(clicon_handle h,
FCGX_Request *r,
char *api_path,
int pi)
{
int retval = -1;
int i;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Parse input data as json into xml */
if (clicon_rpc_xmlput(h, "candidate",
OP_REMOVE,
api_path,
"") < 0)
goto done;
if (clicon_rpc_commit(h, "candidate", "running") < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}
/*! Generic REST PUT method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] dvec Stream input data
* @param[in] post POST instead of PUT
* Example:
curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
PUT:
if the PUT request creates a new resource,
a "201 Created" status-line is returned. If an existing resource is
modified, a "204 No Content" status-line is returned.
POST:
If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in
the response in this case.
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
*/
static int
api_data_put(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
int post)
{
int retval = -1;
int i;
cxobj *xdata = NULL;
cbuf *cbx = NULL;
cxobj *x;
clicon_debug(1, "%s api_path:%s json:%s",
__FUNCTION__,
api_path, data);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json fail", __FUNCTION__);
goto done;
}
if ((cbx = cbuf_new()) == NULL)
goto done;
x = NULL;
while ((x = xml_child_each(xdata, x, -1)) != NULL)
if (clicon_xml2cbuf(cbx, x, 0, 0) < 0)
goto done;
if (clicon_rpc_xmlput(h, "candidate",
OP_MERGE,
api_path,
cbuf_get(cbx)) < 0)
goto done;
if (clicon_rpc_commit(h, "candidate", "running") < 0)
goto done;
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Generic REST method, GET, PUT, DELETE
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
@ -385,14 +101,19 @@ api_data(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__);
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "OPTIONS")==0)
retval = api_data_options(h, r, pcvec, pi, qvec, 0);
retval = api_data_options(h, r);
else if (strcmp(request_method, "HEAD")==0)
retval = api_data_head(h, r, pcvec, pi, qvec);
else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, r, pcvec, pi, qvec);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, 0);
else if (strcmp(request_method, "POST")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data, 1);
retval = api_data_post(h, r, api_path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "PATCH")==0)
retval = api_data_patch(h, r, api_path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, api_path, pi);
else
@ -411,7 +132,7 @@ request_process(clicon_handle h,
char *path;
char *query;
char *method;
char **pvec;
char **pvec = NULL;
int pn;
cvec *qvec = NULL;
cvec *dvec = NULL;
@ -423,7 +144,7 @@ request_process(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", r->envp);
if ((pvec = clicon_strsplit(path, "/", &pn, __FUNCTION__)) == NULL)
if ((pvec = clicon_strsep(path, "/", &pn)) == NULL)
goto done;
if (str2cvec(query, '&', '=', &qvec) < 0)
@ -437,7 +158,12 @@ request_process(clicon_handle h,
clicon_debug(1, "DATA=%s", data);
if (str2cvec(data, '&', '=', &dvec) < 0)
goto done;
method = pvec[2];
if ((method = pvec[2]) == NULL){
retval = notfound(r);
goto done;
}
retval = 0;
test(r, 1);
/* If present, check credentials */
@ -457,6 +183,8 @@ request_process(clicon_handle h,
retval = notfound(r);
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pvec)
free(pvec);
if (dvec)
cvec_free(dvec);
if (qvec)
@ -465,10 +193,43 @@ request_process(clicon_handle h,
cvec_free(pcvec);
if (cb)
cbuf_free(cb);
unchunk_group(__FUNCTION__);
return retval;
}
static int
restconf_terminate(clicon_handle h)
{
yang_spec *yspec;
clicon_debug(0, "%s", __FUNCTION__);
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 */
static clicon_handle _CLICON_HANDLE = NULL;
/*! Signall terminates process
*/
static void
restconf_sig_term(int arg)
{
static int i=0;
if (i++ == 0)
clicon_log(LOG_NOTICE, "%s: %s: pid: %u Signal %d",
__PROGRAM__, __FUNCTION__, getpid(), arg);
else
exit(-1);
if (_CLICON_HANDLE)
restconf_terminate(_CLICON_HANDLE);
clicon_exit_set(); /* checked in event_loop() */
exit(-1);
}
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
@ -485,7 +246,8 @@ usage(clicon_handle h,
"\t-h \t\tHelp\n"
"\t-D \t\tDebug. Log to syslog\n"
"\t-f <file>\tConfiguration file (mandatory)\n"
"\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n",
"\t-d <dir>\tSpecify restconf plugin directory dir (default: %s)\n"
"\t-y <file>\tOverride yang spec file (dont include .yang suffix)\n",
argv0,
restconfdir
);
@ -506,13 +268,14 @@ main(int argc,
char *sockpath;
char *path;
clicon_handle h;
char *yangspec=NULL;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG);
/* 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) {
case 'h':
@ -531,6 +294,9 @@ main(int argc,
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;
@ -540,11 +306,29 @@ main(int argc,
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){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
if (set_signal(SIGINT, restconf_sig_term, NULL) < 0){
clicon_err(OE_DEMON, errno, "Setting signal");
goto done;
}
/* Find and read configfile */
if (clicon_options_main(h) < 0)
goto done;
/* Overwrite yang module with -y option */
if (yangspec){
/* Set revision to NULL, extract dir and module */
char *str = strdup(yangspec);
char *dir = dirname(str);
hash_del(clicon_options(h), (char*)"CLICON_YANG_MODULE_REVISION");
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", basename(yangspec));
clicon_option_str_set(h, "CLICON_YANG_DIR", strdup(dir));
}
/* Initialize plugins group */
if (restconf_plugin_load(h) < 0)
return -1;
@ -592,5 +376,6 @@ main(int argc,
retval = 0;
done:
restconf_plugin_unload(h);
restconf_terminate(h);
return retval;
}

View file

@ -0,0 +1,515 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
/*
* See draft-ietf-netconf-restconf-13.txt [draft]
* See draft-ietf-netconf-restconf-17.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/routing.conf " -s /bin/sh www-data
* This is the interface:
* api/data/profile=<name>/metric=<name> PUT data:enable=<flag>
* api/test
+----------------------------+--------------------------------------+
| 100 Continue | POST accepted, 201 should follow |
| 200 OK | Success with response message-body |
| 201 Created | POST to create a resource success |
| 204 No Content | Success without response message- |
| | body |
| 304 Not Modified | Conditional operation not done |
| 400 Bad Request | Invalid request message |
| 401 Unauthorized | Client cannot be authenticated |
| 403 Forbidden | Access to resource denied |
| 404 Not Found | Resource target or resource node not |
| | found |
| 405 Method Not Allowed | Method not allowed for target |
| | resource |
| 409 Conflict | Resource or lock in use |
| 412 Precondition Failed | Conditional method is false |
| 413 Request Entity Too | too-big error |
| Large | |
| 414 Request-URI Too Large | too-big error |
| 415 Unsupported Media Type | non RESTCONF media type |
| 500 Internal Server Error | operation-failed |
| 501 Not Implemented | unknown-operation |
| 503 Service Unavailable | Recoverable server error |
+----------------------------+--------------------------------------+
Mapping netconf error-tag -> status code
+-------------------------+-------------+
| <error&#8209;tag> | status code |
+-------------------------+-------------+
| in-use | 409 |
| invalid-value | 400 |
| too-big | 413 |
| missing-attribute | 400 |
| bad-attribute | 400 |
| unknown-attribute | 400 |
| bad-element | 400 |
| unknown-element | 400 |
| unknown-namespace | 400 |
| access-denied | 403 |
| lock-denied | 409 |
| resource-denied | 409 |
| rollback-failed | 500 |
| data-exists | 409 |
| data-missing | 409 |
| operation-not-supported | 501 |
| operation-failed | 500 |
| partial-operation | 500 |
| malformed-message | 400 |
+-------------------------+-------------+
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <time.h>
#include <fcgi_stdio.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include "restconf_lib.h"
#include "restconf_methods.h"
/*! REST OPTIONS method
* According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* Minimal support:
* 200 OK
* Allow: HEAD,GET,PUT,DELETE,OPTIONS
*/
int
api_data_options(clicon_handle h,
FCGX_Request *r)
{
clicon_debug(1, "%s", __FUNCTION__);
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE\r\n");
FCGX_FPrintF(r->out, "\r\n");
return 0;
}
/*! Generic GET (both HEAD and GET)
*/
static int
api_data_get_gen(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
int head)
{
int retval = -1;
cg_var *cv;
char *val;
char *v;
int i;
cbuf *path = NULL;
cbuf *path1 = NULL;
cbuf *cbx = NULL;
cxobj **vec = NULL;
yang_spec *yspec;
yang_stmt *y = NULL;
yang_stmt *ykey;
char *name;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
cxobj *xret = NULL;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
if ((path = cbuf_new()) == NULL)
goto done;
if ((path1 = cbuf_new()) == NULL) /* without [] qualifiers */
goto done;
cv = NULL;
cprintf(path1, "/");
/* translate eg a/b=c -> a/[b=c] */
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
name = cv_name_get(cv);
clicon_debug(1, "[%d] cvname:%s", i, name);
clicon_debug(1, "cv2str%d", cv2str(cv, NULL, 0));
if (i == pi){
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
else{
assert(y!=NULL);
if ((y = yang_find_syntax((yang_node*)y, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", name);
goto done;
}
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
v = val;
/* XXX sync with yang */
while((v=index(v, ',')) != NULL){
*v = '\0';
v++;
}
/* Find keys */
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
notfound(r);
goto done;
}
clicon_debug(1, "ykey:%s", ykey->ys_argument);
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(path, "/%s", name);
v = val;
while ((cvi = cvec_each(cvk, cvi)) != NULL){
cprintf(path, "[%s=%s]", cv_string_get(cvi), v);
v += strlen(v)+1;
}
if (val)
free(val);
}
else{
cprintf(path, "%s%s", (i==pi?"":"/"), name);
cprintf(path1, "/%s", name);
}
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){
notfound(r);
goto done;
}
{
cbuf *cb = cbuf_new();
clicon_xml2cbuf(cb, xret, 0, 0);
clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
if ((cbx = cbuf_new()) == NULL)
goto done;
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+json\r\n");
FCGX_FPrintF(r->out, "\r\n");
if (head)
goto ok;
clicon_debug(1, "%s name:%s child:%d", __FUNCTION__, xml_name(xret), xml_child_nr(xret));
vec = xml_childvec_get(xret);
clicon_debug(1, "%s xretnr:%d", __FUNCTION__, xml_child_nr(xret));
if (xml2json_cbuf_vec(cbx, vec, xml_child_nr(xret), 0) < 0)
goto done;
clicon_debug(1, "%s cbuf:%s", __FUNCTION__, cbuf_get(cbx));
FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):"");
FCGX_FPrintF(r->out, "\r\n\r\n");
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
if (path)
cbuf_free(path);
if (path1)
cbuf_free(path1);
if (xret)
xml_free(xret);
return retval;
}
/*! REST HEAD method
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
The HEAD method is sent by the client to retrieve just the header fields
that would be returned for the comparable GET method, without the
response message-body.
* Relation to netconf: none
*/
int
api_data_head(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
{
return api_data_get_gen(h, r, pcvec, pi, qvec, 1);
}
/*! REST GET method
* According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @code
* curl -G http://localhost/restconf/data/interfaces/interface=eth0
* @endcode
* XXX: cant find a way to use Accept request field to choose Content-Type
* I would like to support both xml and json.
* Request may contain
* Accept: application/yang.data+json,application/yang.data+xml
* Response contains one of:
* Content-Type: application/yang.data+xml
* Content-Type: application/yang.data+json
* NOTE: If a retrieval request for a data resource representing a YANG leaf-
* list or list object identifies more than one instance, and XML
* encoding is used in the response, then an error response containing a
* "400 Bad Request" status-line MUST be returned by the server.
* Netconf: <get-config>, <get>
*/
int
api_data_get(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
{
return api_data_get_gen(h, r, pcvec, pi, qvec, 0);
}
/*! Generic edit-config method: PUT/POST/PATCH
*/
static int
api_data_edit(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data,
enum operation_type operation)
{
int retval = -1;
int i;
cxobj *xdata = NULL;
cbuf *cbx = NULL;
cxobj *x;
clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"",
__FUNCTION__,
api_path, data);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json parse fail: %s", __FUNCTION__, data);
goto done;
}
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "<config>");
x = NULL;
while ((x = xml_child_each(xdata, x, -1)) != NULL) {
if (clicon_xml2cbuf(cbx, x, 0, 0) < 0)
goto done;
}
cprintf(cbx, "</config>");
clicon_debug(1, "%s cbx: %s api_path:%s",__FUNCTION__, cbuf_get(cbx), api_path);
if (clicon_rpc_edit_config(h, "candidate",
operation,
api_path,
cbuf_get(cbx)) < 0){
notfound(r);
goto done;
}
if (clicon_rpc_commit(h) < 0)
goto done;
FCGX_SetExitStatus(201, r->out); /* Created */
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! REST POST method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
POST:
If the POST method succeeds, a "201 Created" status-line is returned
and there is no response message-body. A "Location" header
identifying the child resource that was created MUST be present in
the response in this case.
If the data resource already exists, then the POST request MUST fail
and a "409 Conflict" status-line MUST be returned.
* Netconf: <edit-config> (nc:operation="create") | invoke an RPC operation
*/
int
api_data_post(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data)
{
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_CREATE);
}
/*! Generic REST PUT method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* Example:
curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1
*
PUT:
if the PUT request creates a new resource,
a "201 Created" status-line is returned. If an existing resource is
modified, a "204 No Content" status-line is returned.
* Netconf: <edit-config> (nc:operation="create/replace")
*/
int
api_data_put(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data)
{
/* XXX: OP_CREATE? */
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_REPLACE);
}
/*! Generic REST PATCH method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* @param[in] qvec Vector of query string (QUERY_STRING)
* @param[in] data Stream input data
* Netconf: <edit-config> (nc:operation="merge")
*/
int
api_data_patch(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data)
{
return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE);
}
/*! Generic REST DELETE method
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [draft])
* @param[in] pi Offset, where path starts
* Example:
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
* Netconf: <edit-config> (nc:operation="delete")
*/
int
api_data_delete(clicon_handle h,
FCGX_Request *r,
char *api_path,
int pi)
{
int retval = -1;
int i;
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
if (clicon_rpc_edit_config(h, "candidate",
OP_DELETE,
api_path,
"<config/>") < 0){
notfound(r);
goto done;
}
if (clicon_rpc_commit(h) < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
return retval;
}

View file

@ -30,27 +30,34 @@
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifndef _RESTCONF_METHODS_H_
#define _RESTCONF_METHODS_H_
/*
* Constants
*/
#ifndef _CLIXON_XML_DB_RPC_H_
#define _CLIXON_XML_DB_RPC_H_
/*
* Prototypes
*/
int xmldb_get_rpc(clicon_handle h, char *db,
char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put_rpc(clicon_handle h, char *db, cxobj *xt, enum operation_type op);
int xmldb_put_xkey_rpc(clicon_handle h, char *db, char *xk, char *val,
enum operation_type op);
int xmldb_copy_rpc(clicon_handle h, char *from, char *to);
int xmldb_lock_rpc(clicon_handle h, char *db, int pid);
int xmldb_unlock_rpc(clicon_handle h, char *db, int pid);
int xmldb_islocked_rpc(clicon_handle h, char *db);
int api_data_options(clicon_handle h, FCGX_Request *r);
int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cvec *qvec);
int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cvec *qvec);
int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data);
int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data);
int api_data_patch(clicon_handle h, FCGX_Request *r, char *api_path,
cvec *pcvec, int pi,
cvec *qvec, char *data);
int api_data_delete(clicon_handle h, FCGX_Request *r, char *api_path, int pi);
int xmldb_exists_rpc(clicon_handle h, char *db);
int xmldb_delete_rpc(clicon_handle h, char *db);
int xmldb_init_rpc(clicon_handle h, char *db);
#endif /* _CLIXON_XML_DB_RPC_H_ */
#endif /* _RESTCONF_METHODS_H_ */

View file

@ -1,108 +0,0 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
#
# This file is part of CLIXON
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Alternatively, the contents of this file may be used under the terms of
# the GNU General Public License Version 3 or later (the "GPL"),
# in which case the provisions of the GPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of the GPL, and not to allow others to
# use your version of this file under the terms of Apache License version 2,
# indicate your decision by deleting the provisions above and replace them with
# the notice and other provisions required by the GPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the Apache License version 2 or the GPL.
#
# ***** END LICENSE BLOCK *****
#
VPATH = @srcdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
CC = @CC@
CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
libexecdir = @libexecdir@
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
CLIXON_MINOR = @CLIXON_VERSION_MINOR@
# Use this clixon lib for linking
CLIXON_LIB = libclixon.so.$(CLIXON_MAJOR).$(CLIXON_MINOR)
# For dependency
LIBDEPS = $(top_srcdir)/lib/src/$(CLIXON_LIB)
LIBS = -L$(top_srcdir)/lib/src @LIBS@ -l:$(CLIXON_LIB)
CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
SRC =
OBJS = $(SRC:.c=.o)
APPSRC = xmldb_main.c
APPOBJ = $(APPSRC:.c=.o)
APPL = clixon_xmldb
all: $(APPL)
clean:
rm -f $(OBJS) *.core $(APPL) $(APPOBJ)
distclean: clean
rm -f Makefile *~ .depend
# Put demon in bin
# Put other executables in libexec/
# Also create a libexec/ directory for writeable/temporary files.
# Put config file in etc/
install: $(APPL)
install -d $(DESTDIR)$(bindir)
install $(APPL) $(DESTDIR)$(bindir)
install-include:
uninstall:
rm -f $(bindir)/$(APPL)
.SUFFIXES:
.SUFFIXES: .c .o
.c.o:
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS)
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@
TAGS:
find . -name '*.[chyl]' -print | etags -
depend:
$(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) $(APPSRC) > .depend
#include .depend

View file

@ -1,940 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/un.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
/* Command line options to be passed to getopt(3) */
#define XMLDB_OPTS "hDSf:a:p:y:m:r:"
#define DEFAULT_PORT 7878
#define DEFAULT_ADDR "127.0.0.1"
static int
xmldb_send_error(int s,
char *reason)
{
int retval = -1;
cbuf *cb = NULL; /* Outgoing return message */
clicon_log(LOG_NOTICE, "%s", reason);
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<error>%s</error>", reason);
if (write(s, cbuf_get(cb), cbuf_len(cb)+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Process incoming xmldb get message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "get"
* example
* <rpc>
* <get>
* <source><candidate/></source>
* <xpath>/</xpath>
* <vector/> # If set send back list of xpath hits not single tree
* </get>
* </rpc>
* @note restrictions on using only databases called candidate and running
*
*/
static int
xmldb_from_get(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
cxobj *x;
cbuf *cb = NULL; /* Outgoing return message */
char *db;
char *xpath = "/";
cxobj *xt = NULL; /* Top of return tree */
cxobj *xc; /* Child */
cxobj **xvec = NULL;
size_t xlen = 0;
int i;
if (xpath_first(xr, "source/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "source/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Get request: Expected candidate or running as source");
goto drop;
}
if ((x = xpath_first(xr, "xpath")) != NULL)
xpath = xml_body(x);
/* Actual get call */
if (xmldb_get(h, db, xpath, &xt, &xvec, &xlen) < 0)
goto done;
xml_name_set(xt, "config");
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (xvec){
for (i=0; i<xlen; i++){
xc = xvec[i];
if (clicon_xml2cbuf(cb, xc, 0, 0) < 0)
goto done;
}
}
else{
if (clicon_xml2cbuf(cb, xt, 0, 0) < 0)
goto done;
}
if (debug)
fprintf(stderr, "%s: \"%s\" len:%d\n",
__FUNCTION__, cbuf_get(cb), cbuf_len(cb)+1);
if (write(s, cbuf_get(cb), cbuf_len(cb)+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
drop:
retval = 0;
done:
if (xt)
xml_free(xt);
if (xvec)
free(xvec);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Process incoming xmldb put message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "put"
* example
* <rpc>
* <put>
* <target><candidate/></target>
* <default-operation>merge|none|replace</default-operation>
* <config>
* ...
* </config>
* </put>
* </rpc>
* @note restrictions on using only databases called candidate and running
* @note key,val see xmldb_put_xkey
*/
static int
xmldb_from_put(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
cxobj *x;
char *db;
cxobj *xc; /* Child */
char *opstr;
enum operation_type op = OP_REPLACE;
if (xpath_first(xr, "target/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Put request: Expected candidate or running as source");
goto drop;
}
if ((x = xpath_first(xr, "default-operation")) != NULL)
if ((opstr = xml_body(x)) != NULL){
if (strcmp(opstr, "replace") == 0)
op = OP_REPLACE;
else
if (strcmp(opstr, "merge") == 0)
op = OP_MERGE;
else
if (strcmp(opstr, "none") == 0)
op = OP_NONE;
else{
xmldb_send_error(s, "Put request: unrecognized default-operation");
goto drop;
}
}
if ((xc = xpath_first(xr, "config")) != NULL){
/* Actual put call */
if (xmldb_put(h, db, xc, op) < 0)
goto done;
}
if (write(s, "<ok/>", strlen("<ok/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
drop:
retval = 0;
done:
return retval;
}
static int
xmldb_from_put_xkey(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
cxobj *x;
char *db;
char *xkey;
char *val;
char *opstr;
enum operation_type op = OP_REPLACE;
if (xpath_first(xr, "target/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Put request: Expected candidate or running as source");
goto drop;
}
if ((x = xpath_first(xr, "default-operation")) != NULL)
if ((opstr = xml_body(x)) != NULL){
if (strcmp(opstr, "replace") == 0)
op = OP_REPLACE;
else
if (strcmp(opstr, "merge") == 0)
op = OP_MERGE;
else
if (strcmp(opstr, "none") == 0)
op = OP_NONE;
else{
xmldb_send_error(s, "Put xkey request: unrecognized default-operation");
goto drop;
}
}
if ((x = xpath_first(xr, "xkey")) == NULL){
xmldb_send_error(s, "Put xkey request: no xkey");
goto drop;
}
xkey = xml_body(x);
if ((x = xpath_first(xr, "value")) == NULL){
xmldb_send_error(s, "Put xkey request: no value");
goto drop;
}
val = xml_body(x);
if (xmldb_put_xkey(h, db, xkey, val, op) < 0)
goto done;
if (write(s, "<ok/>", strlen("<ok/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming copy message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "exists"
* example
* <rpc>
* <copy>
* <source><candidate/></source>
* <target><running/></target>
* </copy>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_copy(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *source;
char *target;
if (xpath_first(xr, "source/candidate") != NULL)
source = "candidate";
else if (xpath_first(xr, "source/running") != NULL)
source = "running";
else {
xmldb_send_error(s, "Copy request: Expected candidate or running as source");
goto drop;
}
if (xpath_first(xr, "target/candidate") != NULL)
target = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
target = "running";
else {
xmldb_send_error(s, "Copy request: Expected candidate or running as target");
goto drop;
}
if (xmldb_copy(h, source, target) < 0)
goto done;
if (write(s, "<ok/>", strlen("<ok/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming lock message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "exists"
* example
* <rpc>
* <exists>
* <target><candidate/></target>
* <id>43</id>
* </exists>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_lock(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *target;
char *idstr = NULL;
cxobj *x;
if (xpath_first(xr, "target/candidate") != NULL)
target = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
target = "running";
else {
xmldb_send_error(s, "Lock request: Expected candidate or running as target");
goto drop;
}
if ((x = xpath_first(xr, "id")) != NULL){
xmldb_send_error(s, "Lock request: mandatory id not found");
goto drop;
}
idstr = xml_body(x);
if (xmldb_lock(h, target, atoi(idstr)) < 0)
goto done;
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming unlock message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "exists"
* example
* <rpc>
* <unlock>
* <target><candidate/></target>
* <id>43</id>
* </unlock>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_unlock(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *target;
char *idstr = NULL;
cxobj *x;
if (xpath_first(xr, "target/candidate") != NULL)
target = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
target = "running";
else {
xmldb_send_error(s, "Unlock request: Expected candidate or running as target");
goto drop;
}
if ((x = xpath_first(xr, "id")) != NULL){
xmldb_send_error(s, "Unlock request: mandatory id not found");
goto drop;
}
idstr = xml_body(x);
if (xmldb_unlock(h, target, atoi(idstr)) < 0)
goto done;
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming islocked message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "exists"
* example
* <rpc>
* <islocked>
* <target><candidate/></target>
* </islocked>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_islocked(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *db;
int ret;
cbuf *cb = NULL;
if (xpath_first(xr, "target/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Islocked request: Expected candidate or running as source");
goto drop;
}
if ((ret = xmldb_islocked(h, db)) < 0)
goto done;
if (ret > 0){
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(cb, "<locked>%u</locked>", ret);
if (write(s, cbuf_get(cb), cbuf_len(cb)+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
}
else{
if (write(s, "<unlocked/>", strlen("<unlocked/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
}
drop:
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Process incoming exists? message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "exists"
* example
* <rpc>
* <exists>
* <target><candidate/></target>
* </exists>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_exists(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *db;
if (xpath_first(xr, "target/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Exists request: Expected candidate or running as source");
goto drop;
}
/* XXX error and non-exist treated same */
if (xmldb_exists(h, db) == 1){
if (write(s, "<ok/>", strlen("<ok/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
}
else{
xmldb_send_error(s, "DB does not exist");
goto drop;
}
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming xmldb delete message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "delete"
* example
* <rpc>
* <delete>
* <target><candidate/></target>
* </delete>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_delete(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *db;
if (xpath_first(xr, "target/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Delete request: Expected candidate or running as source");
goto drop;
}
if (xmldb_delete(h, db) < 0)
; /* ignore */
if (write(s, "<ok/>", strlen("<ok/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming xmldb init message
* @param[in] s Stream socket
* @param[in] cb Packet buffer
* @param[in] xr XML request node with root in "init"
* example
* <rpc>
* <init>
* <target><candidate/></target>
* </init>
* </rpc>
* @note restrictions on using only databases called candidate and running
*/
static int
xmldb_from_init(clicon_handle h,
int s,
cxobj *xr)
{
int retval = -1;
char *db;
if (xpath_first(xr, "target/candidate") != NULL)
db = "candidate";
else if (xpath_first(xr, "target/running") != NULL)
db = "running";
else {
xmldb_send_error(s, "Init request: Expected candidate or running as source");
goto drop;
}
if (xmldb_init(h, db) < 0)
goto done;
if (write(s, "<ok/>", strlen("<ok/>")+1) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
drop:
retval = 0;
done:
return retval;
}
/*! Process incoming xmldb packet
* @param[in] h Clicon handle
* @param[in] s Stream socket
* @param[in] cbin Incoming packet buffer
* example: <rpc><get></get></rpc>]]>]]>
*/
static int
xmldb_from_client(clicon_handle h,
int s,
cbuf *cbin)
{
int retval = -1;
char *str;
cxobj *xrq = NULL; /* Request (in) */
cxobj *xr;
cxobj *x;
cxobj *xt = NULL;
clicon_debug(1, "xmldb message: \"%s\"", cbuf_get(cbin));
str = cbuf_get(cbin);
str[strlen(str)-strlen("]]>]]>")] = '\0';
/* Parse incoming XML message */
if (clicon_xml_parse_string(&str, &xrq) < 0)
goto done;
if (debug)
xml_print(stderr, xrq);
if ((xr = xpath_first(xrq, "rpc")) != NULL){
if ((x = xpath_first(xr, "get")) != NULL){
if (xmldb_from_get(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "put")) != NULL){
if (xmldb_from_put(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "put-xkey")) != NULL){
if (xmldb_from_put_xkey(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "copy")) != NULL){
if (xmldb_from_copy(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "lock")) != NULL){
if (xmldb_from_lock(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "unlock")) != NULL){
if (xmldb_from_unlock(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "islocked")) != NULL){
if (xmldb_from_islocked(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "exists")) != NULL){
if (xmldb_from_exists(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "init")) != NULL){
if (xmldb_from_init(h, s, x) < 0)
goto done;
}
else if ((x = xpath_first(xr, "delete")) != NULL){
if (xmldb_from_delete(h, s, x) < 0)
goto done;
}
}
else{
xmldb_send_error(s, "Expected rpc as top xml msg");
goto drop;
}
drop:
retval = 0;
done:
if (xrq)
xml_free(xrq);
if (xt)
xml_free(xt);
return retval;
}
/*! stolen from netconf_lib.c */
static int
detect_endtag(char *tag, char ch, int *state)
{
int retval = 0;
if (tag[*state] == ch){
(*state)++;
if (*state == strlen(tag)){
*state = 0;
retval = 1;
}
}
else
*state = 0;
return retval;
}
/*! config_accept_client
*/
int
config_accept_client(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
int s = -1;
struct sockaddr_un from;
socklen_t slen;
ssize_t len;
unsigned char buf[BUFSIZ];
int i;
cbuf *cb = NULL;
int xml_state = 0;
clicon_debug(1, "Accepting client request");
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
return retval;
}
len = sizeof(from);
if ((s = accept(fd, (struct sockaddr*)&from, &slen)) < 0){
clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__);
goto done;
}
if ((len = read(s, buf, sizeof(buf))) < 0){
clicon_err(OE_UNIX, errno, "read");
goto done;
}
for (i=0; i<len; i++){
if (buf[i] == 0)
continue; /* Skip NULL chars (eg from terminals) */
cprintf(cb, "%c", buf[i]);
if (detect_endtag("]]>]]>",
buf[i],
&xml_state)) {
if (xmldb_from_client(h, s, cb) < 0){
goto done;
}
cbuf_reset(cb);
}
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (s != -1)
close(s);
return retval;
}
/*! Create tcp server socket and register callback
*/
static int
server_socket(clicon_handle h,
char *ipv4addr,
uint16_t port)
{
int retval = -1;
int s;
struct sockaddr_in addr;
/* Open control socket */
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
clicon_err(OE_UNIX, errno, "socket");
goto done;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(addr.sin_family, ipv4addr, &addr.sin_addr) != 1){
clicon_err(OE_UNIX, errno, "inet_pton: %s (Expected IPv4 address. Check settings of CLICON_SOCK_FAMILY and CLICON_SOCK)", ipv4addr);
goto done;
}
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0){
clicon_err(OE_UNIX, errno, "%s: bind", __FUNCTION__);
goto done;
}
clicon_debug(1, "Listen on server socket at %s:%hu", ipv4addr, port);
if (listen(s, 5) < 0){
clicon_err(OE_UNIX, errno, "%s: listen", __FUNCTION__);
goto done;
}
if (event_reg_fd(s, config_accept_client, h, "server socket") < 0) {
close(s);
goto done;
}
retval = 0;
done:
return retval;
}
/*
* usage
*/
static void
usage(char *argv0)
{
fprintf(stderr, "usage:%s\n"
"where options are\n"
"\t-h\t\tHelp\n"
"\t-D\t\tDebug\n"
"\t-S\t\tLog on syslog\n"
"\t-f <file>\tCLICON config file\n"
"\t-a <addr>\tIP address\n"
"\t-p <port>\tTCP port\n"
"\t-y <dir>\tYang dir\n"
"\t-m <mod>\tYang main module name\n"
"\t-r <rev>\tYang module revision\n",
argv0
);
exit(0);
}
int
main(int argc, char **argv)
{
char c;
int use_syslog;
clicon_handle h;
uint16_t port;
char *addr = DEFAULT_ADDR;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
/* Defaults */
use_syslog = 0;
port = DEFAULT_PORT;
if ((h = clicon_handle_init()) == NULL)
goto done;
/* getopt in two steps, first find config-file before over-riding options. */
while ((c = getopt(argc, argv, XMLDB_OPTS)) != -1)
switch (c) {
case '?' :
case 'h' : /* help */
usage(argv[0]);
break;
case 'D' : /* debug */
debug = 1;
break;
case 'f': /* config file */
if (!strlen(optarg))
usage(argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break;
case 'S': /* Log on syslog */
use_syslog = 1;
break;
}
/*
* Logs, error and debug to stderr or syslog, set debug level
*/
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO,
use_syslog?CLICON_LOG_SYSLOG:CLICON_LOG_STDERR);
clicon_debug_init(debug, NULL);
/* Find and read configfile */
if (clicon_options_main(h) < 0){
usage(argv[0]);
goto done;
}
/* Now rest of options */
optind = 1;
while ((c = getopt(argc, argv, XMLDB_OPTS)) != -1)
switch (c) {
case 'D': /* Processed earlier, ignore now. */
case 'S':
case 'f':
break;
case 'a': /* address */
clicon_option_str_set(h, "CLICON_XMLDB_ADDR", optarg);
break;
case 'p': /* port */
clicon_option_str_set(h, "CLICON_XMLDB_PORT", optarg);
break;
case 'y': /* yang dir */
clicon_option_str_set(h, "CLICON_YANG_DIR", optarg);
break;
case 'm': /* yang module */
clicon_option_str_set(h, "CLICON_YANG_MODULE_MAIN", optarg);
break;
case 'r': /* yang revision */
clicon_option_str_set(h, "CLICON_YANG_MODULE_REVISION", optarg);
break;
default:
usage(argv[0]);
break;
}
argc -= optind;
argv += optind;
clicon_option_str_set(h, "CLICON_XMLDB_RPC", "0");
if (clicon_yang_dir(h) == NULL){
clicon_err(OE_UNIX, errno, "yang dir not set");
goto done;
}
if (clicon_yang_module_main(h) == NULL){
clicon_err(OE_UNIX, errno, "yang main module not set");
goto done;
}
if (yang_spec_main(h, NULL, 0) < 0)
goto done;
addr = clicon_xmldb_addr(h);
port = clicon_xmldb_port(h);
if (server_socket(h, addr, port) < 0)
goto done;
if (event_loop() < 0)
goto done;
done:
return 0;
}

View file

@ -121,16 +121,6 @@ CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile
# Directory where "running", "candidate" and "startup" are placed
CLICON_XMLDB_DIR localstatedir/APPNAME
# Set if xmldb runs in a separate process (clixon_xmldb).
# If set, also set xmldb_addr and xmldb_port below
# CLICON_XMLDB_RPC 0
# xmldb inet address (if CLICON_XMLDB_RPC)
# CLICON_XMLDB_ADDR
# xmldb tcp port (if CLICON_XMLDB_RPC)
# CLICON_XMLDB_PORT
# Dont include keys in cvec in cli vars callbacks, ie a & k in 'a <b> k <c>' ignored
# CLICON_CLI_VARONLY 1
@ -138,15 +128,3 @@ CLICON_XMLDB_DIR localstatedir/APPNAME
# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;
CLICON_RESTCONF_PATH /www-data/fastcgi_restconf.sock
# Set if you want to use old obsolete cligen expand variable syntax
# Migration: Set to 0 and change all user-defined cli completion callbacks
# E.g. expand_dbvar("db fmt") ->expandv_dbvar("db","fmt") in all your cli spec files
CLICON_CLIGEN_EXPAND_SINGLE_ARG 0
# Set if you want to use old obsolete cligen callback variable syntax
# Migration: Set to 0 and change all user-defined cli callbacks in your cli spec files
# E.g cmd, callback("single arg"); -> cmd, callback("two" "args");
# And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files
CLICON_CLIGEN_CALLBACK_SINGLE_ARG 1

3
configure vendored
View file

@ -4315,7 +4315,7 @@ fi
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/dbctrl/Makefile apps/xmldb/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile doc/Makefile"
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile apps/dbctrl/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/docker/Makefile docker/Makefile docker/cli/Makefile docker/cli/Dockerfile docker/backend/Makefile docker/backend/Dockerfile docker/netconf/Makefile docker/netconf/Dockerfile doc/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@ -5018,7 +5018,6 @@ do
"apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;;
"apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;;
"apps/dbctrl/Makefile") CONFIG_FILES="$CONFIG_FILES apps/dbctrl/Makefile" ;;
"apps/xmldb/Makefile") CONFIG_FILES="$CONFIG_FILES apps/xmldb/Makefile" ;;
"include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
"etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;;
"etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;;

View file

@ -189,7 +189,6 @@ AC_OUTPUT(Makefile
apps/netconf/Makefile
apps/restconf/Makefile
apps/dbctrl/Makefile
apps/xmldb/Makefile
include/Makefile
etc/Makefile
etc/clixonrc

View file

@ -743,7 +743,7 @@ WARN_LOGFILE =
# spaces.
# Note: If this tag is empty the current directory is searched.
INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/config/ ../apps/netconf ../apps/dbctrl
INPUT = ../lib/src/ ../lib/clicon/ ../apps/cli/ ../apps/backend/ ../apps/restconf/ ../apps/netconf ../apps/dbctrl
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View file

@ -91,6 +91,7 @@ Q: How do you change the example?
- routing_backend.c - Commit and validate functions.
- routing_netconf.c - Modify semantics of netconf commands.
Q: How do you check what is in a database?
------------------------------------------
Use clixon_dbctrl. The name of the running or candidate databases are found in the
@ -169,9 +170,31 @@ The validation or commit will then be aborted.
Q: How do you use netconf?
--------------------------
As an alternative to cli configuration, you can use netconf
directly. Easiest is to just pipe netconf commands to the
clixon_netconf application.
As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
Example:
echo "<rpc><get-config><source><candidate/></source><configuration/></get-config></rpc>]]>]]>" | clixon_netconf -f /usr/local/etc/routing.conf
However, more useful is to run clixon_netconf as an SSH
subsystem. Register the subsystem in /etc/sshd_config:
Subsystem netconf /usr/local/bin/clixon_netconf
and then invoke it from a client using
ssh -s netconf <host>
Q: How do you use notifications?
--------------------------------
The example has a prebuilt notification stream called "ROUTING" that triggers every 10s.
You enable the notification either via the cli or via netconf:
cli> notify
cli> Routing notification
Routing notification
...
> clixon_netconf -qf /usr/local/etc/routing.conf
<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>]]>]]>
...

View file

@ -14,7 +14,6 @@ clixon_netconf -f /usr/local/etc/routing.conf
1. Setting data example using netconf
-------------------------------------
<rpc><edit-config><target><candidate/></target><config>
<interfaces>
<interface>
@ -45,7 +44,38 @@ clixon_netconf -f /usr/local/etc/routing.conf
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>
3. Run as docker container
3. Creating notification
------------------------
The example has an example notification triggering every 10s. To start a notification
stream in the session, create a subscription:
<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
<notification><event>Routing notification</event></notification>]]>]]>
...
This can also be triggered via the CLI:
cli> notify
cli> Routing notification
Routing notification
...
4. Downcall
-----------
Clixon has an extension mechanism which can be used to make extended internal
netconf messages to the backend configuration engine. You may need this to
make some special operation that is not covered by standard
netconf functions. The example has a simple "echo" downcall
mechanism that simply echoes what is sent down and is included for
reference. A more realistic downcall would perform some action, such as
reading some status.
Example:
cli> downcall "This is a string"
This is a string
cli>p
5. Run as docker container
--------------------------
cd docker
# look in README

View file

@ -698,4 +698,4 @@
}
}
}
}
}

View file

@ -19,18 +19,11 @@ CLICON_CLI_GENMODEL_COMPLETION 1
# CLICON_CLI_GENMODEL_TYPE VARS
CLICON_CLI_GENMODEL_TYPE VARS
# Set if xmldb runs in a separate process (clixon_xmldb).
# Also set addr and port below
CLICON_XMLDB_RPC 0
# xmldb inet address (if CLICON_XMLDB_RPC)
CLICON_XMLDB_ADDR 127.0.0.1
# xmldb tcp port (if CLICON_XMLDB_RPC)
CLICON_XMLDB_PORT 7878
# Set if you want to use old obsolete cligen callback variable syntax
# Migration: Set to 0 and change all user-defined cli callbacks in your cli spec files
# E.g cmd, callback("single arg"); -> cmd, callback("two" "args");
# And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files
CLICON_CLIGEN_CALLBACK_SINGLE_ARG 0
# Enabled uses "startup" configuration on boot
CLICON_USE_STARTUP_CONFIG 0

View file

@ -42,7 +42,7 @@
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
/* clicon */
#include <cligen/cligen.h>
@ -53,13 +53,16 @@
/* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h>
/* forward */
static int notification_timer_setup(clicon_handle h);
/*! This is called on validate (and commit). Check validity of candidate
*/
int
transaction_validate(clicon_handle h,
transaction_data td)
{
transaction_print(stderr, td);
// transaction_print(stderr, td);
return 0;
}
@ -70,20 +73,62 @@ transaction_commit(clicon_handle h,
transaction_data td)
{
cxobj *target = transaction_target(td); /* wanted XML tree */
cxobj **vec;
cxobj **vec = NULL;
int i;
size_t len;
/* Get all added i/fs */
if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
return -1;
for (i=0; i<len; i++) /* Loop over added i/fs */
xml_print(stdout, vec[i]); /* Print the added interface */
if (debug)
for (i=0; i<len; i++) /* Loop over added i/fs */
xml_print(stdout, vec[i]); /* Print the added interface */
if (vec)
free(vec);
return 0;
}
/*! Routing example notifcation timer handler. Here is where the periodic action is
*/
static int
notification_timer(int fd,
void *arg)
{
int retval = -1;
clicon_handle h = (clicon_handle)arg;
if (backend_notify(h, "ROUTING", 0, "Routing notification") < 0)
goto done;
if (notification_timer_setup(h) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Set up routing notifcation timer
*/
static int
notification_timer_setup(clicon_handle h)
{
struct timeval t, t1;
gettimeofday(&t, NULL);
t1.tv_sec = 10; t1.tv_usec = 0;
timeradd(&t, &t1, &t);
return event_reg_timeout(t, notification_timer, h, "notification timer");
}
static int
routing_downcall(clicon_handle h,
cxobj *xe, /* Request: <rpc><xn></rpc> */
struct client_entry *ce, /* Client session */
cbuf *cbret, /* Reply eg <rpc-reply>... */
void *arg) /* Argument given at register */
{
cprintf(cbret, "<rpc-reply><ok>%s</ok></rpc-reply>", xml_body(xe));
return 0;
}
/*
* Plugin initialization
*/
@ -92,8 +137,15 @@ plugin_init(clicon_handle h)
{
int retval = -1;
if (notification_timer_setup(h) < 0)
goto done;
if (backend_netconf_register_callback(h, routing_downcall,
NULL,
"myrouting"/* Xml tag when callback is made */
) < 0)
goto done;
retval = 0;
// done:
done:
return retval;
}

View file

@ -71,7 +71,7 @@ int
mycallback(clicon_handle h, cvec *cvv, cvec *argv)
{
int retval = -1;
cxobj *xt = NULL;
cxobj *xret = NULL;
cg_var *myvar;
/* Access cligen callback variables */
@ -80,14 +80,51 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
cli_output(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */
/* Show eth0 interfaces config using XPATH */
if (xmldb_get(h, "candidate",
"/interfaces/interface[name=eth0]",
&xt, NULL, NULL) < 0)
if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]",
&xret) < 0)
goto done;
xml_print(stdout, xt);
xml_print(stdout, xret);
retval = 0;
done:
if (xt)
xml_free(xt);
if (xret)
xml_free(xret);
return retval;
}
/*! get argument and send as string to backend as RPC (which returns the string)
*/
int
downcall(clicon_handle h,
cvec *vars,
cvec *argv)
{
int retval = -1;
struct clicon_msg *msg = NULL;
char *str="";
cg_var *cv;
cxobj *xret=NULL;
cxobj *xerr;
cxobj *xdata;
if (cvec_len(vars)==2){
if ((cv = cvec_i(vars, 1)) != NULL)
str = cv_string_get(cv);
}
if ((msg = clicon_msg_encode("<rpc><myrouting>%s</myrouting></rpc>", str)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if ((xdata = xpath_first(xret, "//ok")) != NULL)
cli_output(stdout, "%s\n", xml_body(xdata));
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}

View file

@ -4,42 +4,53 @@ CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="routing_cli";
# Note, when switching to PT, change datamodel to only @datamodel
set @datamodel:ietf-ip, cli_mergev();
set @datamodel:ietf-ip, cli_merge();
#delete("Delete a configuration item") @datamodel:ietf-ipv4-unicast-routing, cli_del();
delete("Delete a configuration item") @datamodel:ietf-ip, cli_delv();
delete("Delete a configuration item") @datamodel:ietf-ip, cli_del();
validate("Validate changes"), cli_validatev();
commit("Commit the changes"), cli_commitv();
quit("Quit Hello"), cli_quitv();
delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_allv("candidate");
validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit();
quit("Quit Hello"), cli_quit();
delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate");
startup("Store running as startup config"), db_copy("running","startup");
no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cliv((int32)0);
debug("Debugging parts of the system"), cli_debug_cliv((int32)1);{
level("Set debug level: 1..n") <level:int32>("Set debug level (0..n)"), cli_debug_cliv();
startup("Store running as startup config"), db_copy("running", "startup");
no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cli((int32)0);
debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
level("Set debug level: 1..n") <level:int32>("Set debug level (0..n)"), cli_debug_backend();
}
discard("Discard edits (rollback 0)"), discard_changesv();
copy("Copy and create a new object") {
interface("Copy interface"){
<name:string expand_dbvar("candidate","/interfaces/interface/name")>("name of interface to copy from") to("Copy to interface") <toname:string>("Name of interface to copy to"), cli_copy_config("candidate","//interface[%s=%s]","name","name","toname");
}
}
discard("Discard edits (rollback 0)"), discard_changes();
compare("Compare running and candidate"), compare_dbs((int32)1);
compare("Compare running and candidate"), compare_dbs((int32)1);
show("Show a particular state of the system"){
xpath("Show configuration") <xpath:string>("XPATH expression"), show_confv_xpath("candidate","/");
compare("Compare candidate and running databases"), compare_dbsv((int32)0);{
xml("Show comparison in xml"), compare_dbsv((int32)0);
text("Show comparison in text"), compare_dbsv((int32)1);
xpath("Show configuration") <xpath:string>("XPATH expression"), show_conf_xpath("candidate");
compare("Compare candidate and running databases"), compare_dbs((int32)0);{
xml("Show comparison in xml"), compare_dbs((int32)0);
text("Show comparison in text"), compare_dbs((int32)1);
}
configuration("Show configuration"), show_confv_as_text("candidate","/");{
xml("Show configuration as XML"), show_confv_as_xml("candidate","/");
netconf("Show configuration as netconf edit-config operation"), show_confv_as_netconf("candidate","/");
text("Show configuration as text"), show_confv_as_text("candidate","/");
cli("Show configuration as cli commands"), show_confv_as_cli("candidate","/");
json("Show configuration as cli commands"), show_confv_as_json("candidate","/");
configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");
netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");
text("Show configuration as text"), cli_show_config("candidate","text","/");
cli("Show configuration as cli commands"), cli_show_config("candidate", "cli", "/");
json("Show configuration as cli commands"), cli_show_config("candidate", "json", "/");
}
}
save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_filev("candidate","filename");
load("Load configuration from XML file") <filename:string>("Filename (local filename)"),load_config_filev("filename","replace");{
replace("Replace candidate with file contents"), load_config_filev("filename","replace");
merge("Merge file with existent candidate"), load_config_filev("filename","merge");
save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_file("candidate","filename");
load("Load configuration from XML file") <filename:string>("Filename (local filename)"),load_config_file("filename", "replace");{
replace("Replace candidate with file contents"), load_config_file("filename", "replace");
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
}
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg");
downcall("This is a downcall") <str:rest>, downcall();
notify("Get notifications from backend"), cli_notify("ROUTING", "1", "text");
no("Negate") notify("Get notifications from backend"), cli_notify("ROUTING", "0", "xml");
lock,cli_lock("candidate");
unlock,cli_unlock("candidate");

View file

@ -37,8 +37,6 @@
#define _CLIXON_FILE_H_
char **clicon_realpath(const char *cwd, char *path, const char *label);
int clicon_file_dirent(const char *dir, struct dirent **ent,
const char *regexp, mode_t type, const char *label);

View file

@ -50,9 +50,7 @@
* Types
*/
/*
* enum gensyntx
* Controls how keywords a generated in CLI syntax / prints from obhect model
/*! Controls how keywords a generated in CLI syntax / prints from object model
* Example syntax a.b[] $!x $y:
* NONE: a b <x> <y>;
* VARS: a b <x> y <y>;
@ -109,9 +107,6 @@ int clicon_cli_varonly_set(clicon_handle h, int val);
int clicon_cli_genmodel_completion(clicon_handle h);
char *clicon_xmldb_dir(clicon_handle h);
int clicon_xmldb_rpc(clicon_handle h);
char *clicon_xmldb_addr(clicon_handle h);
uint16_t clicon_xmldb_port(clicon_handle h);
char *clicon_quiet_mode(clicon_handle h);
enum genmodel_type clicon_cli_genmodel_type(clicon_handle h);

View file

@ -42,136 +42,51 @@
* Types
*/
enum format_enum{
MSG_NOTIFY_TXT, /* means filter works on strings */
MSG_NOTIFY_XML, /* means filter works on xml */
};
/* See also map_type2str in clicon_proto.c */
enum clicon_msg_type{
CLICON_MSG_COMMIT = 1, /* Commit a configuration db->running_db
current state, set running_db. Body is:
1. uint32: (1)snapshot while doing commit, (0) dont
2. uint32: (1)save to startup-config, (0) dont
3. string: name of 'from' database (eg "candidate")
4. string: name of 'to' database (eg "running")
*/
CLICON_MSG_VALIDATE, /* Validate settings in a database. Body is:
1. string: name of database (eg "candidate")
*/
CLICON_MSG_CHANGE, /* Change a (single) database entry:
1. uint32: operation: OP_MERGE/OP_REPLACE/OP_REMOVE
2. uint32: length of value string
3. string: name of database to change (eg "running")
4. string: key
5. string: value (can be NULL)
*/
CLICON_MSG_XMLPUT, /* Send database entries as XML to backend daemon
1. uint32: operation: LV_SET/LV_DELETE
2. string: name of database to change (eg current)
3. string: restconf api path
4. string: XML data
*/
CLICON_MSG_SAVE, /* Save config state from db to a file in backend. Body is:
1. uint32: make snapshot (1), dont(0)
2. string: name of database to save from (eg running)
3. string: filename to write. If snapshot=1, then this
is empty.
*/
CLICON_MSG_LOAD, /* Load config state from file in backend to db via XML. Body is:
1. uint32: whether to replace/initdb before load (1) or
merge (0).
2. string: name of database to load into (eg running)
3. string: filename to load from
*/
CLICON_MSG_COPY, /* Copy from file to file in backend. Body is:
1. string: filename to copy from
2. string: filename to copy to
*/
CLICON_MSG_KILL, /* Kill (other) session:
1. session-id
*/
CLICON_MSG_DEBUG, /* Debug
1. session-id
*/
CLICON_MSG_CALL , /* Backend plugin call request. Body is:
1. struct clicon_msg_call_req *
*/
CLICON_MSG_SUBSCRIPTION, /* Create a new notification subscription.
Body is:
1. int: status off/on
1. int: format (enum format_enum)
2. string: name of notify stream
3. string: filter, if format=xml: xpath, if text: fnmatch */
CLICON_MSG_OK, /* server->client reply */
CLICON_MSG_NOTIFY, /* Notification. Body is:
1. int: loglevel
2. event: log message. */
CLICON_MSG_ERR /* server->client reply.
Body is:
1. uint32: man error category
2. uint32: sub-error
3. string: reason
*/
FORMAT_XML,
FORMAT_JSON,
FORMAT_TEXT,
FORMAT_CLI,
FORMAT_NETCONF
};
/* Protocol message header */
struct clicon_msg {
uint16_t op_len; /* length of message. */
uint16_t op_type; /* message type, see enum clicon_msg_type */
char op_body[0]; /* rest of message, actual data */
};
/* Generic clicon message. Either generic/internal message
or application-specific backend plugin downcall request */
struct clicon_msg_call_req {
uint16_t cr_len; /* Length of total request */
uint16_t cr_op; /* Generic application-defined operation */
char *cr_plugin; /* Name of backend plugin, NULL -> internal
functions */
char *cr_func; /* Function name in plugin (or internal) */
uint16_t cr_arglen; /* App specific argument length */
char *cr_arg; /* App specific argument */
char cr_data[0]; /* Allocated data containng the above */
};
/*
* Prototypes
*/
#ifndef LIBCLICON_API
char *format_int2str(enum format_enum showas);
enum format_enum format_str2int(char *str);
struct clicon_msg *clicon_msg_encode(char *format, ...);
int clicon_msg_decode(struct clicon_msg *msg, cxobj **xml);
int clicon_connect_unix(char *sockpath);
int clicon_rpc_connect_unix(struct clicon_msg *msg,
char *sockpath,
char **data,
uint16_t *datalen,
int *sock0,
const char *label);
int clicon_rpc_connect_unix(struct clicon_msg *msg,
char *sockpath,
char **ret,
int *sock0);
int clicon_rpc_connect_inet(struct clicon_msg *msg,
char *dst,
uint16_t port,
char **data,
uint16_t *datalen,
int *sock0,
const char *label);
int clicon_rpc_connect_inet(struct clicon_msg *msg,
char *dst,
uint16_t port,
char **ret,
int *sock0);
int clicon_rpc(int s, struct clicon_msg *msg, char **data, uint16_t *datalen,
const char *label);
int clicon_rpc(int s, struct clicon_msg *msg, char **xret);
#endif
int clicon_msg_send(int s, struct clicon_msg *msg);
int clicon_msg_rcv(int s, struct clicon_msg **msg,
int *eof, const char *label);
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_reply(int s, uint16_t type, char *data, uint16_t datalen);
int send_msg_reply(int s, char *data, uint16_t datalen);
int send_msg_ok(int s);
int send_msg_err(int s, int err, int suberr, char *format, ...);
int detect_endtag(char *tag, char ch, int *state);
#endif /* _CLIXON_PROTO_H_ */

View file

@ -40,27 +40,25 @@
#ifndef _CLIXON_PROTO_CLIENT_H_
#define _CLIXON_PROTO_CLIENT_H_
int clicon_rpc_commit(clicon_handle h, char *from, char *to);
int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0,
int *sock0);
int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp);
int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr);
int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret);
int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op,
char *api_path, char *xml);
int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2);
int clicon_rpc_delete_config(clicon_handle h, char *db);
int clicon_rpc_lock(clicon_handle h, char *db);
int clicon_rpc_unlock(clicon_handle h, char *db);
int clicon_rpc_close_session(clicon_handle h);
int clicon_rpc_kill_session(clicon_handle h, int session_id);
int clicon_rpc_validate(clicon_handle h, char *db);
int clicon_rpc_change(clicon_handle h, char *db,
enum operation_type op, char *key, char *val);
int clicon_rpc_xmlput(clicon_handle h, char *db, enum operation_type op,
char *api_path, char *xml);
int clicon_rpc_dbitems(clicon_handle h, char *db, char *rx,
char *attr, char *val,
cvec ***cvv, size_t *cvvlen);
int clicon_rpc_save(clicon_handle h, char *dbname, int snapshot, char *filename);
int clicon_rpc_load(clicon_handle h, int replace, char *db, char *filename);
int clicon_rpc_copy(clicon_handle h, char *db1, char *db2);
int clicon_rpc_kill(clicon_handle h, int session_id);
int clicon_rpc_commit(clicon_handle h);
int clicon_rpc_discard_changes(clicon_handle h);
int clicon_rpc_create_subscription(clicon_handle h, char *stream, char *filter,
int *s);
int clicon_rpc_debug(clicon_handle h, int level);
int clicon_rpc_call(clicon_handle h, uint16_t op, char *plugin, char *func,
void *param, uint16_t paramlen,
char **ret, uint16_t *retlen,
const void *label);
int clicon_rpc_subscription(clicon_handle h, int status, char *stream,
enum format_enum format, char *filter, int *s);
#endif /* _CLIXON_PROTO_CLIENT_H_ */

View file

@ -1,177 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Protocol to communicate between clients (eg clixon_cli, clixon_netconf)
* and server (clicon_backend)
*/
#ifndef _CLIXON_PROTO_ENCODE_H_
#define _CLIXON_PROTO_ENCODE_H_
/*
* Prototypes
*/
struct clicon_msg *
clicon_msg_commit_encode(char *dbsrc, char *dbdst,
const char *label);
int
clicon_msg_commit_decode(struct clicon_msg *msg,
char **dbsrc, char **dbdst,
const char *label);
struct clicon_msg *
clicon_msg_validate_encode(char *db,
const char *label);
int
clicon_msg_validate_decode(struct clicon_msg *msg, char **db,
const char *label);
struct clicon_msg *
clicon_msg_change_encode(char *db, uint32_t op, char *key,
char *lvec, uint32_t lvec_len,
const char *label);
int
clicon_msg_change_decode(struct clicon_msg *msg,
char **db, uint32_t *op, char **key,
char **lvec, uint32_t *lvec_len,
const char *label);
struct clicon_msg *
clicon_msg_xmlput_encode(char *db,
uint32_t op,
char *api_path,
char *xml,
const char *label);
int
clicon_msg_xmlput_decode(struct clicon_msg *msg,
char **db,
uint32_t *op,
char **api_path,
char **xml,
const char *label);
struct clicon_msg *
clicon_msg_dbitems_get_reply_encode(cvec **cvecv,
int cveclen,
const char *label);
int
clicon_msg_dbitems_get_reply_decode(char *data,
uint16_t datalen,
cvec ***cvecv,
size_t *cveclen,
const char *label);
struct clicon_msg *
clicon_msg_save_encode(char *db, uint32_t snapshot, char *filename,
const char *label);
int
clicon_msg_save_decode(struct clicon_msg *msg,
char **db, uint32_t *snapshot, char **filename,
const char *label);
struct clicon_msg *
clicon_msg_load_encode(int replace, char *db, char *filename,
const char *label);
int
clicon_msg_load_decode(struct clicon_msg *msg,
int *replace, char **db, char **filename,
const char *label);
struct clicon_msg *
clicon_msg_copy_encode(char *db_src, char *db_dst,
const char *label);
int
clicon_msg_copy_decode(struct clicon_msg *msg,
char **db_src, char **db_dst,
const char *label);
struct clicon_msg *
clicon_msg_kill_encode(uint32_t session_id, const char *label);
int
clicon_msg_kill_decode(struct clicon_msg *msg, uint32_t *session_id,
const char *label);
struct clicon_msg *
clicon_msg_debug_encode(uint32_t level, const char *label);
int
clicon_msg_debug_decode(struct clicon_msg *msg, uint32_t *level,
const char *label);
struct clicon_msg *
clicon_msg_call_encode(uint16_t op, char *plugin, char *func,
uint16_t arglen, void *arg,
const char *label);
int
clicon_msg_call_decode(struct clicon_msg *msg,
struct clicon_msg_call_req **req,
const char *label);
struct clicon_msg *
clicon_msg_subscription_encode(int status,
char *stream,
enum format_enum format,
char *filter,
const char *label);
int clicon_msg_subscription_decode(struct clicon_msg *msg,
int *status,
char **stream,
enum format_enum *format,
char **filter,
const char *label);
struct clicon_msg *
clicon_msg_notify_encode(int level, char *event, const char *label);
int
clicon_msg_notify_decode(struct clicon_msg *msg, int *level,
char **event, const char *label);
struct clicon_msg *clicon_msg_err_encode(uint32_t err, uint32_t suberr,
char *reason, const char *label);
int clicon_msg_err_decode(struct clicon_msg *msg, uint32_t *err, uint32_t *suberr,
char **reason, const char *label);
#endif /* _CLIXON_PROTO_ENCODE_H_ */

View file

@ -54,16 +54,10 @@ static inline char * strdup4(char *str)
/*
* Prototypes
*/
char **clicon_sepsplit (char *string, char *delim, int *nvec, const char *label);
char **clicon_strsplit (char *string, char *delim, int *nvec, const char *label);
char *clicon_strjoin (int argc, char **argv, char *delim, const char *label);
char *clicon_strtrim(char *str, const char *label);
int clicon_sep(char *s, const char sep[2], const char *label, char**a0, char **b0);
char **clicon_strsep(char *string, char *delim, int *nvec0);
char *clicon_strjoin (int argc, char **argv, char *delim);
#ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t);
#endif /* ! HAVE_STRNDUP */
int clicon_strmatch(const char *str, const char *regexp, char **match);
char *clicon_strsub(char *str, char *from, char *to);
#endif /* _CLIXON_STRING_H_ */

View file

@ -40,7 +40,7 @@
* Types
*/
/* Netconf operation type */
enum operation_type{ /* edit-config */
enum operation_type{ /* edit-configo */
OP_MERGE, /* merge config-data */
OP_REPLACE,/* replace or create config-data */
OP_CREATE, /* create config data, error if exist */
@ -98,6 +98,7 @@ cxobj *xml_child_i(cxobj *xn, int i);
cxobj *xml_child_i_set(cxobj *xt, int i, cxobj *xc);
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type);
cxobj **xml_childvec_get(cxobj *x);
int xml_childvec_set(cxobj *x, int len);
cxobj *xml_new(char *name, cxobj *xn_parent);
cxobj *xml_new_spec(char *name, cxobj *xn_parent, void *spec);
@ -124,6 +125,7 @@ int clicon_xml_parse_file(int fd, cxobj **xml_top, char *endtag);
/* XXX obsolete */
#define clicon_xml_parse_string(str, x) clicon_xml_parse_str((*str), x)
int clicon_xml_parse_str(char *str, cxobj **xml_top);
int clicon_xml_parse(cxobj **cxtop, char *format, ...);
int xml_copy(cxobj *x0, cxobj *x1);
cxobj *xml_dup(cxobj *x0);
@ -137,5 +139,7 @@ int xml_apply_ancestor(cxobj *xn, xml_applyfn_t fn, void *arg);
int xml_body_parse(cxobj *xb, enum cv_type type, cg_var **cvp);
int xml_body_int32(cxobj *xb, int32_t *val);
int xml_body_uint32(cxobj *xb, uint32_t *val);
int xml_operation(char *opstr, enum operation_type *op);
char *xml_operation2str(enum operation_type op);
#endif /* _CLIXON_XML_H */

View file

@ -42,18 +42,16 @@
int yang2xmlkeyfmt(yang_stmt *ys, int inclkey, char **xkfmt);
int xmlkeyfmt2key(char *xkfmt, cvec *cvv, char **xk);
int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk);
int xmldb_get(clicon_handle h, char *db, char *xpath,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put(clicon_handle h, char *db, cxobj *xt, enum operation_type op);
int xmldb_put_tree(clicon_handle h, char *db, char *api_path,
cxobj *xt, enum operation_type op);
int xmldb_put_xkey(clicon_handle h, char *db,
char *xkey, char *val,
enum operation_type op);
int xmldb_dump_local(FILE *f, char *dbfilename, char *rxkey);
int xmldb_put(clicon_handle h, char *db, enum operation_type op,
char *api_path, cxobj *xt);
int xmldb_dump(FILE *f, char *dbfilename, char *rxkey);
int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid);
int xmldb_unlock(clicon_handle h, char *db, int pid);
int xmldb_unlock_all(clicon_handle h, int pid);
int xmldb_islocked(clicon_handle h, char *db);
int xmldb_exists(clicon_handle h, char *db);
int xmldb_delete(clicon_handle h, char *db);

View file

@ -53,7 +53,7 @@ enum {
* Prototypes
*/
int xml2txt(FILE *f, cxobj *x, int level);
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt, const char *label);
int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt);
int xml_yang_validate(cxobj *xt, yang_stmt *ys) ;
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0);

View file

@ -68,8 +68,8 @@ SRC = clixon_sig.c clixon_qdb.c clixon_log.c clixon_err.c clixon_event.c \
clixon_json.c \
clixon_yang.c clixon_yang_type.c \
clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_encode.c clixon_proto_client.c \
clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_xml_db_rpc.c
clixon_proto.c clixon_proto_client.c \
clixon_xsl.c clixon_sha1.c clixon_xml_db.c
YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
lex.clixon_yang_parse.o clixon_yang_parse.tab.o \

View file

@ -94,6 +94,7 @@ static struct errvec EV[] = {
{"UNIX error", OE_UNIX},
{"Syslog error", OE_SYSLOG},
{"Routing demon error", OE_ROUTING},
{"XML error", OE_XML},
{"Plugins", OE_PLUGIN},
{"Yang error", OE_YANG},
{"FATAL", OE_FATAL},

View file

@ -62,99 +62,6 @@
#include "clixon_string.h"
#include "clixon_file.h"
/*
* Resolve the real path of a given 'path', following symbolic links and '../'.
* If 'path' relative, it will be resolved based on the currnt working
* directory 'cwd'. The response is a 2 entry vector of strings. The first
* entry is the resolved path and the second is the part of the path which
* actually exist.
*/
char **
clicon_realpath(const char *cwd, char *path, const char *label)
{
char **ret = NULL;
char *rest;
char **vec, **vec2;
int nvec, nvec2;
char *p;
char *rp = NULL;
char *ptr;
int i;
struct passwd *pwd;
char cwdbuf[PATH_MAX];
/* Prepend 'cwd' if not absolute */
if (path[0] == '/')
p = path;
else {
if (cwd == NULL || strlen(cwd) == 0)
cwd = getcwd(cwdbuf, sizeof(cwdbuf));
else if (cwd[0] == '~') {
if((pwd = getpwuid(getuid())) == NULL)
goto catch;
cwd = pwd->pw_dir;
}
p = chunk_sprintf(__FUNCTION__, "%s%s/%s",
(cwd[0]=='/' ? "" : "/"), cwd, path);
}
if (p == NULL)
goto catch;
/* Make a local copy of 'path' */
if ((path = chunkdup(p, strlen(p)+1, __FUNCTION__)) == NULL)
goto catch;
/* Find the smallest portion of the path that exist and run realpath() */
while(strlen(p) && ((rp = realpath(p, NULL)) == NULL)) {
if((ptr = strrchr(p, '/')) == NULL)
break;
*ptr = '\0';
}
if(rp == NULL)
goto catch;
/* Use the result of realpath() and the rest of 'path' untouched, to
form a new path */
rest = path + strlen(p);
ptr = chunk_sprintf(__FUNCTION__, "%s%s", rp, rest);
p = ptr;
/* Split path based on '/'. Loop through vector from the end and copy
each entry into a new vector, skipping '..' and it's previous directory
as well as all '.' */
vec = clicon_strsplit (p, "/", &nvec, __FUNCTION__);
vec2 = chunk(nvec * sizeof(char *), __FUNCTION__);
nvec2 = i = nvec;
while(--i >= 0) {
if(strcmp(vec[i], "..") == 0)
i--; /* Skip previous */
else if(strcmp(vec[i], ".") == 0)
/* do nothing */ ;
else
vec2[--nvec2] = vec[i];
}
/* Create resulting vector */
if ((ret = chunk(sizeof(char *) * 2, label)) != NULL) {
if((ret[0] = clicon_strjoin(nvec-nvec2, &vec2[nvec2], "/", label)) == NULL) {
unchunk(ret);
ret = NULL;
}
if ((ret[1] = chunkdup(rp, strlen(rp)+1, label)) == NULL) {
unchunk(ret[0]);
unchunk(ret);
ret = NULL;
}
}
catch:
if(rp)
free(rp);
unchunk_group(__FUNCTION__);
return ret;
}
/*
* qsort function
*/
@ -345,24 +252,3 @@ clicon_file_copy(char *src,
}
#ifdef NOTUSED
/*
* (un)lock a whole file.
* Arguments:
* fd - File descriptor
* cmd - F_GETLK, F_SETLK, F_SETLKW
* type - F_RDLCK, F_WRLCK, F_UNLCK
*/
int
file_lock(int fd, int cmd, int type)
{
struct flock lock;
lock.l_type = type;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
return fcntl(fd, cmd, &lock);
}
#endif

View file

@ -131,11 +131,7 @@ int
json_scan_exit(struct clicon_json_yacc_arg *jy)
{
yy_delete_buffer(jy->jy_lexbuf);
#if defined(YY_FLEX_SUBMINOR_VERSION) && YY_FLEX_SUBMINOR_VERSION >= 9
clixon_json_parselex_destroy(); /* modern */
#else
yy_init = 1; /* This does not quite free all buffers */
#endif
return 0;
}

View file

@ -224,7 +224,7 @@ json_current_body(struct clicon_json_yacc_arg *jy,
*/
/* top: json -> value is also possible */
json : object J_EOF { clicon_debug(1,"json->object"); YYACCEPT; }
json : value J_EOF { clicon_debug(1,"json->object"); YYACCEPT; }
;
value : J_TRUE { json_current_body(_JY, "true");}

View file

@ -196,10 +196,6 @@ clicon_option_default(clicon_hash_t *copt)
if (hash_add(copt, "CLICON_CLI_GENMODEL_COMPLETION", "0", strlen("0")+1) < 0)
goto catch;
}
if (!hash_lookup(copt, "CLICON_XMLDB_RPC")){
if (hash_add(copt, "CLICON_XMLDB_RPC", "0", strlen("0")+1) < 0)
goto catch;
}
retval = 0;
catch:
unchunk_group(__FUNCTION__);
@ -616,35 +612,6 @@ clicon_xmldb_dir(clicon_handle h)
return clicon_option_str(h, "CLICON_XMLDB_DIR");
}
/*! Set if xmldb runs in a separate process (clixon_xmldb). */
int
clicon_xmldb_rpc(clicon_handle h)
{
char *s;
if ((s = clicon_option_str(h, "CLICON_XMLDB_RPC")) == NULL)
return 0; /* default 0 */
return atoi(s);
}
/*! Get xmldb inet address */
char *
clicon_xmldb_addr(clicon_handle h)
{
return clicon_option_str(h, "CLICON_XMLDB_ADDR");
}
/*! Get port for xmldb address in case of AF_INET or AF_INET6 */
uint16_t
clicon_xmldb_port(clicon_handle h)
{
char *s;
if ((s = clicon_option_str(h, "CLICON_XMLDB_PORT")) == NULL)
return -1;
return atoi(s);
}
/*! Get YANG specification
* Must use hash functions directly since they are not strings.
*/

View file

@ -121,13 +121,12 @@ clicon_proc_run (char *cmd,
sigfn_t oldhandler = NULL;
sigset_t oset;
argv = clicon_sepsplit (cmd, " \t", &argc, __FUNCTION__);
argv = clicon_strsep(cmd, " \t", &argc);
if (!argv)
return -1;
if (pipe (outfd) == -1)
goto done;
signal_get_mask(&oset);
set_signal(SIGINT, clicon_proc_sigint, &oldhandler);
@ -194,7 +193,8 @@ clicon_proc_run (char *cmd,
signal_set_mask (&oset);
set_signal(SIGINT, oldhandler, NULL);
unchunk_group (__FUNCTION__);
if(argv)
free(argv);
return retval;
}
@ -216,7 +216,7 @@ clicon_proc_daemon (char *cmd)
struct rlimit
rlim;
argv = clicon_sepsplit (cmd, " \t", &argc, NULL);
argv = clicon_strsep(cmd, " \t", &argc);
if (!argv)
return -1;
@ -260,7 +260,8 @@ clicon_proc_daemon (char *cmd)
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (argv)
free(argv);
return (retval);
}

View file

@ -68,47 +68,117 @@
#include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_sig.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_proto.h"
#include "clixon_proto_encode.h"
static int _atomicio_sig = 0;
struct map_type2str{
enum clicon_msg_type mt_type;
char *mt_str; /* string as in 4.2.4 in RFC 6020 */
/*! Formats (showas) derived from XML
*/
struct formatvec{
char *fv_str;
int fv_int;
};
/* Mapping between yang keyword string <--> clicon constants */
static const struct map_type2str msgmap[] = {
{CLICON_MSG_COMMIT, "commit"},
{CLICON_MSG_VALIDATE, "validate"},
{CLICON_MSG_CHANGE, "change"},
{CLICON_MSG_XMLPUT, "xmlput"},
{CLICON_MSG_SAVE, "save"},
{CLICON_MSG_LOAD, "load"},
{CLICON_MSG_COPY, "copy"},
{CLICON_MSG_KILL, "kill"},
{CLICON_MSG_DEBUG, "debug"},
{CLICON_MSG_CALL, "call"},
{CLICON_MSG_SUBSCRIPTION, "subscription"},
{CLICON_MSG_OK, "ok"},
{CLICON_MSG_NOTIFY, "notify"},
{CLICON_MSG_ERR, "err"},
{-1, NULL},
static struct formatvec _FORMATS[] = {
{"xml", FORMAT_XML},
{"text", FORMAT_TEXT},
{"json", FORMAT_JSON},
{"cli", FORMAT_CLI},
{"netconf", FORMAT_NETCONF},
{NULL, -1}
};
static char *
msg_type2str(enum clicon_msg_type type)
/*! Translate from numeric format to string representation
* @param[in] showas Format value (see enum format_enum)
* @retval str String value
*/
char *
format_int2str(enum format_enum showas)
{
const struct map_type2str *mt;
struct formatvec *fv;
for (mt = &msgmap[0]; mt->mt_str; mt++)
if (mt->mt_type == type)
return mt->mt_str;
return NULL;
for (fv=_FORMATS; fv->fv_int != -1; fv++)
if (fv->fv_int == showas)
break;
return fv?(fv->fv_str?fv->fv_str:"unknown"):"unknown";
}
/*! Translate from string to numeric format representation
* @param[in] str String value
* @retval enum Format value (see enum format_enum)
*/
enum format_enum
format_str2int(char *str)
{
struct formatvec *fv;
for (fv=_FORMATS; fv->fv_int != -1; fv++)
if (strcmp(fv->fv_str, str) == 0)
break;
return fv?fv->fv_int:-1;
}
/*! Encode a clicon netconf message
* @param[in] format Variable agrument list format an XML netconf string
* @retval msg Clicon message to send to eg clicon_msg_send()
*/
struct clicon_msg *
clicon_msg_encode(char *format, ...)
{
va_list args;
int xmllen;
int len;
struct clicon_msg *msg = NULL;
int hdrlen = sizeof(*msg);
va_start(args, format);
xmllen = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
len = hdrlen + xmllen;
if ((msg = (struct clicon_msg *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_len = htons(len);
/* body */
va_start(args, format);
vsnprintf(msg->op_body, xmllen, format, args);
va_end(args);
return msg;
}
/*! Decode a clicon netconf message
* @param[in] msg CLICON msg
* @param[out] xml XML parse tree
*/
int
clicon_msg_decode(struct clicon_msg *msg,
cxobj **xml)
{
int retval = -1;
char *xmlstr;
/* body */
xmlstr = msg->op_body;
clicon_debug(1, "%s %s", __FUNCTION__, xmlstr);
if (clicon_xml_parse_str(xmlstr, xml) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Open local connection using unix domain sockets
* @param[in] sockpath Unix domain file path
* @retval s socket
* @retval -1 error
*/
int
clicon_connect_unix(char *sockpath)
@ -147,13 +217,19 @@ atomicio_sig_handler(int arg)
_atomicio_sig++;
}
/*! Ensure all of data on socket comes through. fn is either read or write
* @param[in] fn I/O function, ie read/write
* @param[in] fd File descriptor, eg socket
* @param[in] s0 Buffer to read to or write from
* @param[in] n Number of bytes to read/write, loop until done
*/
static ssize_t
atomicio(ssize_t (*fn) (int, void *, size_t), int fd, void *_s, size_t n)
atomicio(ssize_t (*fn) (int, void *, size_t),
int fd,
void *s0,
size_t n)
{
char *s = _s;
char *s = s0;
ssize_t res, pos = 0;
while (n > pos) {
@ -177,6 +253,9 @@ atomicio(ssize_t (*fn) (int, void *, size_t), int fd, void *_s, size_t n)
return (pos);
}
/*! Print message on debug. Log if syslog, stderr if not
* @param[in] msg CLICON msg
*/
static int
msg_dump(struct clicon_msg *msg)
{
@ -202,14 +281,18 @@ msg_dump(struct clicon_msg *msg)
return 0;
}
/*! Send a CLICON netconf message
* @param[in] s socket (unix or inet) to communicate with backend
* @param[out] msg CLICON msg data reply structure. Free with free()
*/
int
clicon_msg_send(int s,
struct clicon_msg *msg)
{
int retval = -1;
clicon_debug(2, "%s: send msg seq=%d len=%d",
__FUNCTION__, ntohs(msg->op_type), ntohs(msg->op_len));
clicon_debug(2, "%s: send msg len=%d",
__FUNCTION__, ntohs(msg->op_len));
if (debug > 2)
msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write,
@ -223,7 +306,7 @@ clicon_msg_send(int s,
}
/*! Receive a CLICON message on a UNIX domain socket
/*! Receive a CLICON message
*
* XXX: timeout? and signals?
* There is rudimentary code for turning on signals and handling them
@ -233,18 +316,15 @@ clicon_msg_send(int s,
* behaviour.
* Now, ^C will interrupt the whole process, and this may not be what you want.
*
* @param[in] s UNIX domain socket to communicate with backend
* @param[out] msg CLICON msg data reply structure. allocated using CLICON chunks,
* freed by caller with unchunk*(...,label)
* @param[in] s socket (unix or inet) to communicate with backend
* @param[out] msg CLICON msg data reply structure. Free with free()
* @param[out] eof Set if eof encountered
* @param[in] label Label used in chunk allocation and deallocation.
* Note: caller must ensure that s is closed if eof is set after call.
*/
int
clicon_msg_rcv(int s,
struct clicon_msg **msg,
int *eof,
const char *label)
struct clicon_msg **msg,
int *eof)
{
int retval = -1;
struct clicon_msg hdr;
@ -271,10 +351,10 @@ clicon_msg_rcv(int s,
goto done;
}
mlen = ntohs(hdr.op_len);
clicon_debug(2, "%s: rcv msg seq=%d, len=%d",
__FUNCTION__, ntohs(hdr.op_type), mlen);
if ((*msg = (struct clicon_msg *)chunk(mlen, label)) == NULL){
clicon_err(OE_CFG, errno, "%s: chunk", __FUNCTION__);
clicon_debug(2, "%s: rcv msg len=%d",
__FUNCTION__, mlen);
if ((*msg = (struct clicon_msg *)malloc(mlen)) == NULL){
clicon_err(OE_CFG, errno, "malloc");
goto done;
}
memcpy(*msg, &hdr, hlen);
@ -295,25 +375,27 @@ clicon_msg_rcv(int s,
return retval;
}
/*! Connect to server, send an clicon_msg message and wait for result.
* Compared to clicon_rpc, this is a one-shot rpc: open, send, get reply and close.
* NOTE: this is dependent on unix domain
/*! Connect to server, send a clicon_msg message and wait for result using unix socket
*
* @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* @param[in] sockpath Unix domain file path
* @param[out] retdata Returned data as string netconf xml tree.
* @param[out] sock0 Return socket in case of asynchronous notify
* @retval 0 OK
* @retval -1 Error
* @see clicon_rpc But this is one-shot rpc: open, send, get reply and close.
*/
int
clicon_rpc_connect_unix(struct clicon_msg *msg,
char *sockpath,
char **data,
uint16_t *datalen,
int *sock0,
const char *label)
char **retdata,
int *sock0)
{
int retval = -1;
int s = -1;
struct stat sb;
clicon_debug(1, "Send %s msg on %s",
msg_type2str(ntohs(msg->op_type)), sockpath);
clicon_debug(1, "Send msg on %s", sockpath);
/* special error handling to get understandable messages (otherwise ENOENT) */
if (stat(sockpath, &sb) < 0){
clicon_err(OE_PROTO, errno, "%s: config daemon not running?", sockpath);
@ -325,7 +407,7 @@ clicon_rpc_connect_unix(struct clicon_msg *msg,
}
if ((s = clicon_connect_unix(sockpath)) < 0)
goto done;
if (clicon_rpc(s, msg, data, datalen, label) < 0)
if (clicon_rpc(s, msg, retdata) < 0)
goto done;
if (sock0 != NULL)
*sock0 = s;
@ -336,24 +418,29 @@ clicon_rpc_connect_unix(struct clicon_msg *msg,
return retval;
}
/*! Connect to server, send an clicon_msg message and wait for result using an inet socket
* Compared to clicon_rpc, this is a one-shot rpc: open, send, get reply and close.
/*! Connect to server, send a clicon_msg message and wait for result using an inet socket
* This uses unix domain socket communication
* @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* @param[in] dst IPv4 address
* @param[in] port TCP port
* @param[out] retdata Returned data as string netconf xml tree.
* @param[out] sock0 Return socket in case of asynchronous notify
* @retval 0 OK
* @retval -1 Error
* @see clicon_rpc But this is one-shot rpc: open, send, get reply and close.
*/
int
clicon_rpc_connect_inet(struct clicon_msg *msg,
char *dst,
uint16_t port,
char **data,
uint16_t *datalen,
int *sock0,
const char *label)
char **retdata,
int *sock0)
{
int retval = -1;
int s = -1;
struct sockaddr_in addr;
clicon_debug(1, "Send %s msg to %s:%hu",
msg_type2str(ntohs(msg->op_type)), dst, port);
clicon_debug(1, "Send msg to %s:%hu", dst, port);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
@ -371,7 +458,7 @@ clicon_rpc_connect_inet(struct clicon_msg *msg,
close(s);
goto done;
}
if (clicon_rpc(s, msg, data, datalen, label) < 0)
if (clicon_rpc(s, msg, retdata) < 0)
goto done;
if (sock0 != NULL)
*sock0 = s;
@ -391,29 +478,24 @@ clicon_rpc_connect_inet(struct clicon_msg *msg,
*
* @param[in] s Socket to communicate with backend
* @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* @param[out] data Returned data as byte-strin exclusing header.
* Deallocate w unchunk...(..., label)
* @param[out] datalen Length of returned data
* @param[in] label Label used in chunk allocation.
* @param[out] xret Returned data as netconf xml tree.
* @retval 0 OK
* @retval -1 Error
*/
int
clicon_rpc(int s,
struct clicon_msg *msg,
char **data,
uint16_t *datalen,
const char *label)
clicon_rpc(int s,
struct clicon_msg *msg,
char **ret)
{
int retval = -1;
struct clicon_msg *reply;
int eof;
uint32_t err;
uint32_t suberr;
char *reason;
enum clicon_msg_type type;
char *data = NULL;
cxobj *cx = NULL;
if (clicon_msg_send(s, msg) < 0)
goto done;
if (clicon_msg_rcv(s, &reply, &eof, label) < 0)
if (clicon_msg_rcv(s, &reply, &eof) < 0)
goto done;
if (eof){
clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__);
@ -421,37 +503,31 @@ clicon_rpc(int s,
errno = ESHUTDOWN;
goto done;
}
type = ntohs(reply->op_type);
switch (type){
case CLICON_MSG_OK:
if (data != NULL) {
*data = reply->op_body;
*datalen = ntohs(reply->op_len) - sizeof(*reply);
}
break;
case CLICON_MSG_ERR:
if (clicon_msg_err_decode(reply, &err, &suberr, &reason, label) < 0)
data = reply->op_body; /* assume string */
if (ret && data)
if ((*ret = strdup(data)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
if (debug)
clicon_err(err, suberr, "%s msgtype:%hu", reason, ntohs(msg->op_type));
else
clicon_err(err, suberr, "%s", reason);
goto done;
break;
default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %hu",
__FUNCTION__, type);
goto done;
break;
}
}
retval = 0;
done:
if (cx)
xml_free(cx);
if (reply)
free(reply);
return retval;
}
/*! Send a clicon_msg message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client
* @param[in] data Returned data as byte-string.
* @param[in] datalen Length of returned data XXX may be unecessary if always string?
* @retval 0 OK
* @retval -1 Error
*/
int
send_msg_reply(int s,
uint16_t type,
char *data,
uint16_t datalen)
{
@ -463,7 +539,6 @@ send_msg_reply(int s,
if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL)
goto done;
memset(reply, 0, len);
reply->op_type = htons(type);
reply->op_len = htons(len);
if (datalen > 0)
memcpy(reply->op_body, data, datalen);
@ -475,57 +550,55 @@ send_msg_reply(int s,
return retval;
}
int
send_msg_ok(int s)
{
return send_msg_reply(s, CLICON_MSG_OK, NULL, 0);
}
/*! Send a clicon_msg NOTIFY message asynchronously to client
*
* @param[in] s Socket to communicate with client
* @param[in] level
* @param[in] event
* @retval 0 OK
* @retval -1 Error
*/
int
send_msg_notify(int s,
int level,
char *event)
{
int retval = -1;
struct clicon_msg *msg;
int retval = -1;
struct clicon_msg *msg = NULL;
if ((msg=clicon_msg_notify_encode(level, event, __FUNCTION__)) == NULL)
if ((msg=clicon_msg_encode("<notification><event>%s</event></notification>", event)) == NULL)
goto done;
if (clicon_msg_send(s, msg) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (msg)
free(msg);
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
* @param[in,out] state A state integer holding how far we have parsed.
* @retval 0 No, we havent detected end tag
* @retval 1 Yes, we have detected end tag!
*/
int
send_msg_err(int s, int err, int suberr, char *format, ...)
detect_endtag(char *tag,
char ch,
int *state)
{
va_list args;
char *reason;
int len;
int retval = -1;
struct clicon_msg *msg;
int retval = 0;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((reason = (char *)chunk(len, __FUNCTION__)) == NULL)
return -1;
memset(reason, 0, len);
va_start(args, format);
vsnprintf(reason, len, format, args);
va_end(args);
if ((msg=clicon_msg_err_encode(clicon_errno, clicon_suberrno,
reason, __FUNCTION__)) == NULL)
goto done;
if (clicon_msg_send(s, msg) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (tag[*state] == ch){
(*state)++;
if (*state == strlen(tag)){
*state = 0;
retval = 1;
}
}
else
*state = 0;
return retval;
}

View file

@ -49,6 +49,7 @@
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/syslog.h>
/* cligen */
#include <cligen/cligen.h>
@ -56,38 +57,36 @@
/* clicon */
#include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_log.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_proto.h"
#include "clixon_err.h"
#include "clixon_xml.h"
#include "clixon_proto_encode.h"
#include "clixon_proto_client.h"
/*! Internal rpc function
/*! Send internal netconf rpc from client to backend
* @param[in] h CLICON handle
* @param[in] msg Encoded message
* @param[out] ret Return value from backend server (reply)
* @param[out] retlen Length of return value
* @param[in] msg Encoded message. Deallocate woth free
* @param[out] xret Return value from backend as netconf xml tree. Free w xml_free
* @param[inout] sock0 If pointer exists, do not close socket to backend on success
* and return it here. For keeping a notify socket open
* @param[in] label Chunk label for deallocating return values
* Deallocate with unchunk_group(label)
* Note: sock0 is if connection should be persistent, like a notification/subscribe api
*/
static int
int
clicon_rpc_msg(clicon_handle h,
struct clicon_msg *msg,
char **ret,
uint16_t *retlen,
int *sock0,
const char *label)
cxobj **xret0,
int *sock0)
{
int retval = -1;
char *sock;
int port;
char *retdata = NULL;
cxobj *xret = NULL;
if ((sock = clicon_sock(h)) == NULL){
clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
@ -96,7 +95,7 @@ clicon_rpc_msg(clicon_handle h,
/* What to do if inet socket? */
switch (clicon_sock_family(h)){
case AF_UNIX:
if (clicon_rpc_connect_unix(msg, sock, ret, retlen, sock0, label) < 0){
if (clicon_rpc_connect_unix(msg, sock, &retdata, sock0) < 0){
#if 0
if (errno == ESHUTDOWN)
/* Maybe could reconnect on a higher layer, but lets fail
@ -115,231 +114,553 @@ clicon_rpc_msg(clicon_handle h,
clicon_err(OE_FATAL, 0, "CLICON_SOCK_PORT not set");
goto done;
}
if (clicon_rpc_connect_inet(msg, sock, port, ret, retlen, sock0, label) < 0)
if (clicon_rpc_connect_inet(msg, sock, port, &retdata, sock0) < 0)
goto done;
break;
}
clicon_debug(1, "%s retdata:%s", __FUNCTION__, retdata);
if (retdata &&
clicon_xml_parse_str(retdata, &xret) < 0)
goto done;
if (xret0){
*xret0 = xret;
xret = NULL;
}
retval = 0;
done:
if (retdata)
free(retdata);
if (xret)
xml_free(xret);
return retval;
}
/*! Commit changes send a commit request to backend daemon
* @param[in] h CLICON handle
* @param[in] from name of 'from' database (eg "candidate")
* @param[in] db name of 'to' database (eg "running")
* @retval 0 Copy current->candidate
/*! Generic xml netconf clicon rpc
* 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] sp Socket pointer for notification, otherwise NULL
* @see clicon_rpc_netconf_xml xml as tree instead of string
*/
int
clicon_rpc_commit(clicon_handle h,
char *from,
char *to)
clicon_rpc_netconf(clicon_handle h,
char *xmlstr,
cxobj **xret,
int *sp)
{
int retval = -1;
struct clicon_msg *msg;
struct clicon_msg *msg = NULL;
if ((msg=clicon_msg_commit_encode(from, to, __FUNCTION__)) == NULL)
if ((msg = clicon_msg_encode("%s", xmlstr)) < 0)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
if (clicon_rpc_msg(h, msg, xret, sp) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (msg)
free(msg);
return retval;
}
/*! Send validate request to backend daemon
* @param[in] h CLICON handle
* @param[in] db Name of database
* @retval 0
/*! Generic xml netconf clicon rpc
* Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle
* @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
* @see clicon_rpc_netconf xml as string instead of tree
*/
int
clicon_rpc_validate(clicon_handle h,
char *db)
clicon_rpc_netconf_xml(clicon_handle h,
cxobj *xml,
cxobj **xret,
int *sp)
{
int retval = -1;
struct clicon_msg *msg;
cbuf *cb = NULL;
if ((msg=clicon_msg_validate_encode(db, __FUNCTION__)) == NULL)
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
}
if (clicon_xml2cbuf(cb, xml, 0, 0) < 0)
goto done;
if (clicon_rpc_netconf(h, cbuf_get(cb), xret, sp) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send database change request to backend daemon, variant for xmldb
* Same as clicon_proto_change just with a string
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: set, delete, (merge?)
* @param[in] key Database key
* @param[in] value value as string
* @retval 0 OK
* @retval -1 Error
* @note special case: remove all: key:"/" op:OP_REMOVE
/*! Generate clicon error function call from Netconf error message
* @param[in] xerr Netconf error message on the level: <rpc-reply><rpc-error>
*/
int
clicon_rpc_change(clicon_handle h,
char *db,
enum operation_type op,
char *key,
char *val)
clicon_rpc_generate_error(cxobj *xerr)
{
int retval = -1;
struct clicon_msg *msg;
int retval = -1;
cbuf *cb = NULL;
cxobj *x;
if ((msg = clicon_msg_change_encode(db,
op,
key,
val,
val?strlen(val)+1:0,
__FUNCTION__)) == NULL)
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
}
if ((x=xpath_first(xerr, "error-type"))!=NULL)
cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-tag"))!=NULL)
cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-message"))!=NULL)
cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-info"))!=NULL)
clicon_xml2cbuf(cb, xml_child_i(x,0), 0, 0);
clicon_err_fn("Clixon", 0, OE_XML, 0, "%s", cbuf_get(cb));
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Get database configuration
* Same as clicon_proto_change just with a cvec instead of lvec
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] xpath XPath (or "")
* @param[out] xt XML tree. must be freed by caller with xml_free
* @retval 0 OK
* @retval -1 Error, fatal or xml
* @code
* cxobj *xt = NULL;
* if (clicon_rpc_get_config(h, "running", "/", &xt) < 0)
* err;
* if (xt)
* xml_free(xt);
* @endcode
*/
int
clicon_rpc_get_config(clicon_handle h,
char *db,
char *xpath,
cxobj **xt)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr;
cxobj *xd;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><get-config><source><%s/></source>", db);
if (xpath && strlen(xpath))
cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
cprintf(cb, "</get-config></rpc>");
if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
if ((xd = xpath_first(xret, "//data/config")) == NULL)
if ((xd = xml_new("config", NULL)) == NULL)
goto done;
if (xt){
if (xml_rm(xd) < 0)
goto done;
*xt = xd;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (cb)
cbuf_free(cb);
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Send database entries as XML to backend daemon
* Same as clicon_proto_change just with a cvec instead of lvec
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: OP_MERGE, OP_REPLACE
* @param[in] api_path restconf API Path (or "")
* @param[in] xml XML string. Ex: <a>..</a><b>...</b>
* @param[in] xml XML string. Ex: <config><a>..</a><b>...</b></config>
* @retval 0 OK
* @retval -1 Error
* @note xml arg need to have <config> as top element
* @code
* if (clicon_rpc_edit_config(h, "running", OP_MERGE, "/",
* "<config><a>4</a></config>") < 0)
* err;
* @endcode
*/
int
clicon_rpc_xmlput(clicon_handle h,
char *db,
enum operation_type op,
char *api_path,
char *xml)
clicon_rpc_edit_config(clicon_handle h,
char *db,
enum operation_type op,
char *api_path,
char *xmlstr)
{
int retval = -1;
struct clicon_msg *msg;
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_xmlput_encode(db,
(uint32_t)op,
api_path,
xml,
__FUNCTION__)) == NULL)
if ((cb = cbuf_new()) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
cprintf(cb, "<rpc><edit-config><target><%s/></target>", db);
cprintf(cb, "<default-operation>%s</default-operation>",
xml_operation2str(op));
if (api_path && strlen(api_path))
cprintf(cb, "<filter type=\"restconf\" select=\"%s\"/>", api_path);
if (xmlstr)
cprintf(cb, "%s", xmlstr);
cprintf(cb, "</edit-config></rpc>");
if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
/*! Send database save request to backend daemon
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] snapshot Save to snapshot file
* @param[in] filename Save to file (backend file-system)
*/
int
clicon_rpc_save(clicon_handle h,
char *db,
int snapshot,
char *filename)
{
int retval = -1;
struct clicon_msg *msg;
if ((msg=clicon_msg_save_encode(db, snapshot, filename,
__FUNCTION__)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
/*! Send database load request to backend daemon
* @param[in] h CLICON handle
* @param[in] replace 0: merge with existing data, 1:replace completely
* @param[in] db Name of database
* @param[in] filename Load from file (backend file-system)
*/
int
clicon_rpc_load(clicon_handle h,
int replace,
char *db,
char *filename)
{
int retval = -1;
struct clicon_msg *msg;
if ((msg=clicon_msg_load_encode(replace, db, filename,
__FUNCTION__)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (xret)
xml_free(xret);
if (cb)
cbuf_free(cb);
if (msg)
free(msg);
return retval;
}
/*! Send a request to backend to copy a file from one location to another
* Note this assumes the backend can access these files and (usually) assumes
* clients and servers have the access to the same filesystem.
* @param[in] h CLICON handle
* @param[in] db1 src database, eg "candidate"
* @param[in] db2 dst database, eg "running"
* @param[in] h CLICON handle
* @param[in] db1 src database, eg "running"
* @param[in] db2 dst database, eg "startup"
* @code
* if (clicon_rpc_copy_config(h, "running", "startup") < 0)
* err;
* @endcode
*/
int
clicon_rpc_copy(clicon_handle h,
char *db1,
char *db2)
clicon_rpc_copy_config(clicon_handle h,
char *db1,
char *db2)
{
int retval = -1;
struct clicon_msg *msg;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg=clicon_msg_copy_encode(db1, db2, __FUNCTION__)) == NULL)
if ((msg = clicon_msg_encode("<rpc><copy-config><source><%s/></source><target><%s/></target></copy-config></rpc>", db1, db2)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Send a kill session request to backend server
* @param[in] h CLICON handle
* @param[in] session_id Id of session to kill
/*! Send a request to backend to delete a config database
* @param[in] h CLICON handle
* @param[in] db database, eg "running"
* @code
* if (clicon_rpc_delete_config(h, "startup") < 0)
* err;
* @endcode
*/
int
clicon_rpc_kill(clicon_handle h,
int session_id)
clicon_rpc_delete_config(clicon_handle h,
char *db)
{
int retval = -1;
struct clicon_msg *msg;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg=clicon_msg_kill_encode(session_id, __FUNCTION__)) == NULL)
if ((msg = clicon_msg_encode("<rpc><delete-config><target><%s/></target></delete-config></rpc>", db)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Lock a database
* @param[in] h CLICON handle
* @param[in] db database, eg "running"
*/
int
clicon_rpc_lock(clicon_handle h,
char *db)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><lock><target><%s/></target></lock></rpc>", db)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Unlock a database
* @param[in] h CLICON handle
* @param[in] db database, eg "running"
*/
int
clicon_rpc_unlock(clicon_handle h,
char *db)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><unlock><target><%s/></target></unlock></rpc>", db)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Close a (user) session
* @param[in] h CLICON handle
*/
int
clicon_rpc_close_session(clicon_handle h)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><close-session/></rpc>")) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Kill other user sessions
* @param[in] h CLICON handle
* @param[in] session_id Session id of other user session
*/
int
clicon_rpc_kill_session(clicon_handle h,
int session_id)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><kill-session><session-id>%d</session-id></kill-session></rpc>", session_id)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Send validate request to backend daemon
* @param[in] h CLICON handle
* @param[in] db Name of database
* @retval 0 OK
*/
int
clicon_rpc_validate(clicon_handle h,
char *db)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
cxobj *x;
if ((msg = clicon_msg_encode("<rpc><validate><source><%s/></source></validate></rpc>", db)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
x=xpath_first(xerr, "error-message");
clicon_log(LOG_ERR, "Validate failed: \"%s\". Edit and try again or discard changes", x?xml_body(x):"");
goto done;
}
retval = 0;
done:
if (msg)
free(msg);
if (xret)
xml_free(xret);
return retval;
}
/*! Commit changes send a commit request to backend daemon
* @param[in] h CLICON handle
* @retval 0 OK
*/
int
clicon_rpc_commit(clicon_handle h)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><commit/></rpc>")) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Discard all changes in candidate / revert to running
* @param[in] h CLICON handle
* @retval 0 OK
*/
int
clicon_rpc_discard_changes(clicon_handle h)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><discard-changes/></rpc>")) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
/*! Create a new notification subscription
* @param[in] h Clicon handle
* @param{in] stream name of notificatio/log stream (CLICON is predefined)
* @param{in] filter message filter, eg xpath for xml notifications
* @param[out] s0 socket returned where notification mesages will appear
* @note When using netconf create-subsrciption,status and format is not supported
*/
int
clicon_rpc_create_subscription(clicon_handle h,
char *stream,
char *filter,
int *s0)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><create-subscription>"
"<stream>%s</stream>"
"<filter>%s</filter>"
"</create-subscription></rpc>",
stream?stream:"", filter?filter:"")) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, s0) < 0)
goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0;
done:
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval;
}
@ -352,107 +673,28 @@ clicon_rpc_debug(clicon_handle h,
int level)
{
int retval = -1;
struct clicon_msg *msg;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg=clicon_msg_debug_encode(level, __FUNCTION__)) == NULL)
if ((msg = clicon_msg_encode("<rpc><debug><level>%d</level></debug></rpc>", level)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0)
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
}
/*! An rpc call from a frontend module to a function in a backend module
*
* A CLI/netconf frontend module can make a functional call to a backend
* module and get return value back.
* The backend module needs to be specified (XXX would be nice to avoid this)
* parameters can be sent, and value returned.
* A function (func) must be defined in the backend module (plugin)
* An example signature of such a downcall function is:
* @code
* char name[16];
* char *ret;
* uint16_t retlen;
* clicon_rpc_call(h, 0, "my-backend-plugin", "my_fn", name, 16,
* &ret, &retlen, __FUNCTION__);
* unchunk_group(__FUNCTION__); # deallocate 'ret'
* @endcode
* Backend example function:
* @code
int
downcall(clicon_handle h, uint16_t op, uint16_t len, void *arg,
uint16_t *reply_data_len, void **reply_data)
* @endcode
*
* @param[in] h Clicon handle
* @param[in] op Generic application-defined operation
* @param[in] plugin Name of backend plugin (XXX look in backend plugin dir)
* @param[in] func Name of function i backend (ie downcall above) as string
* @param[in] param Input parameter given to function (void* arg in downcall)
* @param[in] paramlen Length of input parameter
* @param[out] ret Returned data as byte-string. Deallocate w unchunk...(..., label)
* @param[out] retlen Length of returned data
* @param[in] label Label used in chunk (de)allocation. Use:
* unchunk_group(label) to deallocate
*/
int
clicon_rpc_call(clicon_handle h,
uint16_t op,
char *plugin,
char *func,
void *param,
uint16_t paramlen,
char **ret,
uint16_t *retlen,
const void *label)
{
int retval = -1;
struct clicon_msg *msg;
if ((msg = clicon_msg_call_encode(op, plugin, func,
paramlen, param,
label)) == NULL)
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
if (clicon_rpc_msg(h, msg, (char**)ret, retlen, NULL, label) < 0)
}
if (xpath_first(xret, "//rpc-reply/ok") == NULL){
clicon_err(OE_XML, 0, "rpc error"); /* XXX extract info from rpc-error */
goto done;
retval = 0;
done:
return retval;
}
/*! Create a new notification subscription
* @param[in] h Clicon handle
* @param[in] status 0: stop existing notification stream 1: start new stream.
* @param{in] stream name of notificatio/log stream (CLICON is predefined)
* @param{in] filter message filter, eg xpath for xml notifications
* @param[out] s0 socket returned where notification mesages will appear
*/
int
clicon_rpc_subscription(clicon_handle h,
int status,
char *stream,
enum format_enum format,
char *filter,
int *s0)
{
struct clicon_msg *msg;
int retval = -1;
if ((msg=clicon_msg_subscription_encode(status, stream, format, filter,
__FUNCTION__)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, s0, __FUNCTION__) < 0)
goto done;
if (status == 0 && s0){
close(*s0);
*s0 = -1;
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
done:
if (msg)
free(msg);
if (xret)
xml_free(xret);
return retval;
}

View file

@ -1,924 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
*
* Protocol to communicate between clients (eg clicon_cli, clicon_netconf)
* and server (clicon_backend)
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <syslog.h>
#include <signal.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
#include "clixon_chunk.h"
#include "clixon_sig.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_proto.h"
#include "clixon_proto_encode.h"
struct clicon_msg *
clicon_msg_commit_encode(char *dbsrc, char *dbdst,
const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
clicon_debug(2, "%s: dbsrc: %s dbdst: %s",
__FUNCTION__,
dbsrc, dbdst);
p = 0;
len = sizeof(*msg) + 2*sizeof(uint32_t) + strlen(dbsrc) + 1 +
strlen(dbdst) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_COMMIT);
msg->op_len = htons(len);
/* body */
strncpy(msg->op_body+p, dbsrc, len-p-hdrlen);
p += strlen(dbsrc)+1;
strncpy(msg->op_body+p, dbdst, len-p-hdrlen);
p += strlen(dbdst)+1;
return msg;
}
int
clicon_msg_commit_decode(struct clicon_msg *msg,
char **dbsrc, char **dbdst,
const char *label)
{
int p;
p = 0;
/* body */
if ((*dbsrc = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*dbsrc)+1;
if ((*dbdst = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*dbdst)+1;
clicon_debug(2, "%s: dbsrc: %s dbdst: %s", __FUNCTION__, *dbsrc, *dbdst);
return 0;
}
struct clicon_msg *
clicon_msg_validate_encode(char *db, const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
clicon_debug(2, "%s: db: %s", __FUNCTION__, db);
len = sizeof(*msg) + strlen(db) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_VALIDATE);
msg->op_len = htons(len);
/* body */
strncpy(msg->op_body, db, len-hdrlen);
return msg;
}
int
clicon_msg_validate_decode(struct clicon_msg *msg, char **db, const char *label)
{
/* body */
if ((*db = chunk_sprintf(label, "%s", msg->op_body)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf", __FUNCTION__);
return -1;
}
clicon_debug(2, "%s: db: %s", __FUNCTION__, *db);
return 0;
}
struct clicon_msg *
clicon_msg_change_encode(char *db,
uint32_t op,
char *key,
char *str,
uint32_t str_len,
const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
uint32_t tmp;
clicon_debug(2, "%s: op: %d str_len: %d db: %s key: '%s'",
__FUNCTION__,
op, str_len, db, key);
p = 0;
len = sizeof(*msg) + 2*sizeof(uint32_t) + strlen(db) + 1 +
strlen(key) + str_len;
if (str_len)
len++; /* if str not null add end of string */
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_CHANGE);
msg->op_len = htons(len);
/* body */
tmp = htonl(op);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
tmp = htonl(str_len);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
strncpy(msg->op_body+p, db, len-p-hdrlen);
p += strlen(db)+1;
strncpy(msg->op_body+p, key, len-p-hdrlen);
p += strlen(key)+1;
if (str_len){
memcpy(msg->op_body+p, str, str_len);
p += str_len;
}
return msg;
}
int
clicon_msg_change_decode(struct clicon_msg *msg,
char **db,
uint32_t *op,
char **key,
char **str,
uint32_t *str_len,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*op = ntohl(tmp);
p += sizeof(uint32_t);
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*str_len = ntohl(tmp);
p += sizeof(uint32_t);
if ((*db = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db)+1;
if ((*key = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*key)+1;
if (*str_len){
if ((*str = chunk(*str_len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk",
__FUNCTION__);
return -1;
}
memcpy(*str, msg->op_body+p, *str_len);
p += *str_len;
}
else
*str = NULL;
clicon_debug(2, "%s: op: %d str_len: %d db: %s key: '%s'",
__FUNCTION__,
*op, *str_len, *db, *key);
return 0;
}
/*! Encode xmlput / edit of database content
* @param[in] db Name of database
* @param[in] op set|merge|delete. See lv_op_t
* @param[in] api_path restconf api path
* @param[in] xml XML data string
* @param[in] label Memory chunk label
* @retval msg Encoded message
* @retval NULL Error
*/
struct clicon_msg *
clicon_msg_xmlput_encode(char *db,
uint32_t op,
char *api_path,
char *xml,
const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
uint32_t tmp;
clicon_debug(2, "%s: op: %d db: %s api_path: %s xml: %s",
__FUNCTION__,
op, db, api_path, xml);
p = 0;
hdrlen = sizeof(*msg);
len = sizeof(*msg) + sizeof(uint32_t) + strlen(db) + 1 + strlen(api_path) + 1 +strlen(xml) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_XMLPUT);
msg->op_len = htons(len);
/* body */
tmp = htonl(op);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
strncpy(msg->op_body+p, db, len-p-hdrlen);
p += strlen(db)+1;
strncpy(msg->op_body+p, api_path, len-p-hdrlen);
p += strlen(api_path)+1;
strncpy(msg->op_body+p, xml, len-p-hdrlen);
p += strlen(xml)+1;
return msg;
}
/*! Decode xmlput / edit of database content
* @param[in] msg Incoming message to be decoded
* @param[out] db Name of database
* @param[out] op set|merge|delete. See lv_op_t
* @param[out] api_path restconf api path
* @param[out] xml XML data string
* @param[in] label Memory chunk label
* @retval 0 OK
* @retval -1 Error
*/
int
clicon_msg_xmlput_decode(struct clicon_msg *msg,
char **db,
uint32_t *op,
char **api_path,
char **xml,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*op = ntohl(tmp);
p += sizeof(uint32_t);
if ((*db = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db)+1;
if ((*api_path = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*api_path)+1;
if ((*xml = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*xml)+1;
clicon_debug(2, "%s: op: %d db: %s api_path: %s xml: %s",
__FUNCTION__,
*op, *db, *api_path, *xml);
return 0;
}
struct clicon_msg *
clicon_msg_save_encode(char *db, uint32_t snapshot, char *filename,
const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
uint32_t tmp;
clicon_debug(2, "%s: snapshot: %d db: %s filename: %s",
__FUNCTION__,
snapshot, db, filename);
p = 0;
hdrlen = sizeof(*msg);
len = sizeof(*msg) + sizeof(uint32_t) + strlen(db) + 1;
if (!snapshot)
len += strlen(filename) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_SAVE);
msg->op_len = htons(len);
/* body */
tmp = htonl(snapshot);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
strncpy(msg->op_body+p, db, len-p-hdrlen);
p += strlen(db)+1;
if (!snapshot){
strncpy(msg->op_body+p, filename, len-p-hdrlen);
p += strlen(filename)+1;
}
return msg;
}
int
clicon_msg_save_decode(struct clicon_msg *msg,
char **db, uint32_t *snapshot, char **filename,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*snapshot = ntohl(tmp);
p += sizeof(uint32_t);
if ((*db = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db)+1;
if (*snapshot == 0){
if ((*filename = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*filename)+1;
}
clicon_debug(2, "%s: snapshot: %d db: %s filename: %s",
__FUNCTION__,
*snapshot, *db, *filename);
return 0;
}
struct clicon_msg *
clicon_msg_load_encode(int replace, char *db, char *filename, const char *label)
{
struct clicon_msg *msg;
int hdrlen = sizeof(*msg);
uint16_t len;
uint32_t tmp;
int p;
clicon_debug(2, "%s: replace: %d db: %s filename: %s",
__FUNCTION__,
replace, db, filename);
p = 0;
len = sizeof(*msg) + sizeof(uint32_t) + strlen(db) + 1 + strlen(filename) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_LOAD);
msg->op_len = htons(len);
/* body */
tmp = htonl(replace);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
strncpy(msg->op_body+p, db, len-p-hdrlen);
p += strlen(db)+1;
strncpy(msg->op_body+p, filename, len-p-hdrlen);
p += strlen(filename)+1;
return msg;
}
int
clicon_msg_load_decode(struct clicon_msg *msg,
int *replace,
char **db,
char **filename,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*replace = ntohl(tmp);
p += sizeof(uint32_t);
if ((*db = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db)+1;
if ((*filename = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*filename)+1;
clicon_debug(2, "%s: %d db: %s filename: %s",
__FUNCTION__,
ntohs(msg->op_type),
*db, *filename);
return 0;
}
struct clicon_msg *
clicon_msg_copy_encode(char *db_src, char *db_dst,
const char *label)
{
struct clicon_msg *msg;
int hdrlen = sizeof(*msg);
uint16_t len;
int p;
clicon_debug(2, "%s: db_src: %s db_dst: %s",
__FUNCTION__,
db_src, db_dst);
p = 0;
len = hdrlen + strlen(db_src) + 1 + strlen(db_dst) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_COPY);
msg->op_len = htons(len);
/* body */
strncpy(msg->op_body+p, db_src, len-p-hdrlen);
p += strlen(db_src)+1;
strncpy(msg->op_body+p, db_dst, len-p-hdrlen);
p += strlen(db_dst)+1;
return msg;
}
int
clicon_msg_copy_decode(struct clicon_msg *msg,
char **db_src, char **db_dst,
const char *label)
{
int p;
p = 0;
/* body */
if ((*db_src = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db_src)+1;
if ((*db_dst = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*db_dst)+1;
clicon_debug(2, "%s: db_src: %s db_dst: %s",
__FUNCTION__,
*db_src, *db_dst);
return 0;
}
struct clicon_msg *
clicon_msg_kill_encode(uint32_t session_id, const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int p;
uint32_t tmp;
clicon_debug(2, "%s: %d", __FUNCTION__, session_id);
p = 0;
len = sizeof(*msg) + sizeof(uint32_t);
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_KILL);
msg->op_len = htons(len);
/* body */
tmp = htonl(session_id);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
return msg;
}
int
clicon_msg_kill_decode(struct clicon_msg *msg,
uint32_t *session_id,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*session_id = ntohl(tmp);
p += sizeof(uint32_t);
clicon_debug(2, "%s: session-id: %u", __FUNCTION__, *session_id);
return 0;
}
struct clicon_msg *
clicon_msg_debug_encode(uint32_t level, const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int p;
uint32_t tmp;
clicon_debug(2, "%s: %d", __FUNCTION__, label);
p = 0;
len = sizeof(*msg) + sizeof(uint32_t);
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_DEBUG);
msg->op_len = htons(len);
/* body */
tmp = htonl(level);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
return msg;
}
int
clicon_msg_debug_decode(struct clicon_msg *msg,
uint32_t *level,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*level = ntohl(tmp);
p += sizeof(uint32_t);
clicon_debug(2, "%s: session-id: %u", __FUNCTION__, *level);
return 0;
}
struct clicon_msg *
clicon_msg_call_encode(uint16_t op,
char *plugin,
char *func,
uint16_t arglen,
void *arg,
const char *label)
{
struct clicon_msg *msg;
struct clicon_msg_call_req *req;
int hdrlen = sizeof(*msg);
int len;
clicon_debug(2, "%s: %d plugin: %s func: %s arglen: %d",
__FUNCTION__, op, plugin, func, arglen);
len =
hdrlen +
sizeof(struct clicon_msg_call_req) +
strlen(plugin) + 1 +
strlen(func) + 1 +
arglen;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_CALL);
msg->op_len = htons(len);
/* req */
req = (struct clicon_msg_call_req *)msg->op_body;
req->cr_len = htons(len - hdrlen);
req->cr_op = htons(op);
req->cr_plugin = req->cr_data;
strncpy(req->cr_plugin, plugin, strlen(plugin));
req->cr_func = req->cr_plugin + strlen(req->cr_plugin) + 1;
strncpy(req->cr_func, func, strlen(func));
req->cr_arglen = htons(arglen);
req->cr_arg = req->cr_func + strlen(req->cr_func) + 1;
memcpy(req->cr_arg, arg, arglen);
return msg;
}
int
clicon_msg_call_decode(struct clicon_msg *msg,
struct clicon_msg_call_req **req,
const char *label)
{
uint16_t len;
struct clicon_msg_call_req *r;
r = (struct clicon_msg_call_req *)msg->op_body;
len = ntohs(r->cr_len);
if ((*req = chunk(len, label)) == NULL) {
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return -1;
}
memcpy(*req, r, len);
(*req)->cr_len = ntohs(r->cr_len);
(*req)->cr_op = ntohs(r->cr_op);
(*req)->cr_arglen = ntohs(r->cr_arglen);
(*req)->cr_plugin = (*req)->cr_data;
(*req)->cr_func = (*req)->cr_plugin + strlen((*req)->cr_plugin) +1;
(*req)->cr_arg = (*req)->cr_func + strlen((*req)->cr_func) +1;
return 0;
}
struct clicon_msg *
clicon_msg_subscription_encode(int status,
char *stream,
enum format_enum format,
char *filter,
const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
int tmp;
clicon_debug(2, "%s: %d %d %s %s", __FUNCTION__, status, format, stream, filter);
p = 0;
assert(filter);
len = hdrlen + sizeof(uint32_t) + sizeof(uint32_t) + strlen(stream) + 1 + strlen(filter) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_SUBSCRIPTION);
msg->op_len = htons(len);
/* body */
tmp = htonl(status);
memcpy(msg->op_body+p, &tmp, sizeof(int));
p += sizeof(int);
tmp = htonl(format);
memcpy(msg->op_body+p, &tmp, sizeof(int));
p += sizeof(int);
strncpy(msg->op_body+p, stream, len-p-hdrlen);
p += strlen(stream)+1;
strncpy(msg->op_body+p, filter, len-p-hdrlen);
p += strlen(filter)+1;
return msg;
}
int
clicon_msg_subscription_decode(struct clicon_msg *msg,
int *status,
char **stream,
enum format_enum *format,
char **filter,
const char *label)
{
int p;
int tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(int));
*status = ntohl(tmp);
p += sizeof(int);
memcpy(&tmp, msg->op_body+p, sizeof(int));
*format = ntohl(tmp);
p += sizeof(int);
if ((*stream = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*stream)+1;
if ((*filter = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*filter)+1;
clicon_debug(2, "%s: %d %s %d %s", __FUNCTION__, *status, *filter, *stream, *filter);
return 0;
}
struct clicon_msg *
clicon_msg_notify_encode(int level, char *event, const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
int tmp;
clicon_debug(2, "%s: %d %s", __FUNCTION__, level, event);
p = 0;
hdrlen = sizeof(*msg);
len = sizeof(*msg) + sizeof(uint32_t) + strlen(event) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_NOTIFY);
msg->op_len = htons(len);
/* body */
tmp = htonl(level);
memcpy(msg->op_body+p, &tmp, sizeof(int));
p += sizeof(int);
strncpy(msg->op_body+p, event, len-p-hdrlen);
p += strlen(event)+1;
return msg;
}
int
clicon_msg_notify_decode(struct clicon_msg *msg,
int *level, char **event,
const char *label)
{
int p;
int tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(int));
*level = ntohl(tmp);
p += sizeof(int);
if ((*event = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*event)+1;
clicon_debug(2, "%s: %d %s", __FUNCTION__, *level, *event);
return 0;
}
struct clicon_msg *
clicon_msg_err_encode(uint32_t err, uint32_t suberr, char *reason, const char *label)
{
struct clicon_msg *msg;
uint16_t len;
int hdrlen = sizeof(*msg);
int p;
uint32_t tmp;
clicon_debug(2, "%s: %d %d %s", __FUNCTION__, err, suberr, reason);
p = 0;
hdrlen = sizeof(*msg);
len = sizeof(*msg) + 2*sizeof(uint32_t) + strlen(reason) + 1;
if ((msg = (struct clicon_msg *)chunk(len, label)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk", __FUNCTION__);
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_ERR);
msg->op_len = htons(len);
/* body */
tmp = htonl(err);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
tmp = htonl(suberr);
memcpy(msg->op_body+p, &tmp, sizeof(uint32_t));
p += sizeof(uint32_t);
strncpy(msg->op_body+p, reason, len-p-hdrlen);
p += strlen(reason)+1;
return msg;
}
int
clicon_msg_err_decode(struct clicon_msg *msg,
uint32_t *err, uint32_t *suberr, char **reason,
const char *label)
{
int p;
uint32_t tmp;
p = 0;
/* body */
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*err = ntohl(tmp);
p += sizeof(uint32_t);
memcpy(&tmp, msg->op_body+p, sizeof(uint32_t));
*suberr = ntohl(tmp);
p += sizeof(uint32_t);
if ((*reason = chunk_sprintf(label, "%s", msg->op_body+p)) == NULL){
clicon_err(OE_PROTO, errno, "%s: chunk_sprintf",
__FUNCTION__);
return -1;
}
p += strlen(*reason)+1;
clicon_debug(2, "%s: %d %d %s",
__FUNCTION__,
*err, *suberr, *reason);
return 0;
}

View file

@ -53,228 +53,89 @@
#include "clixon_string.h"
#include "clixon_err.h"
/*! Split string into a vector based on character delimiters
/*! Split string into a vector based on character delimiters. Using malloc
*
* The given string is split into a vector where the delimiter can be
* any of the characters in the specified delimiter string.
*
* The vector returned is one single memory chunk that must be unchunked
* The vector returned is one single memory block that must be freed
* by the caller
*
* @param[in] string String to be split
* @param[in] delim String of delimiter characters
* @param[out] nvec Number of entries in returned vector
* @param[in] label Chunk label for returned vector
* @retval vec Vector of strings. Free with unchunk
* @retval NULL Error
* @see clicon_strsplit Operates on full string delimiters rather than
* individual character delimiters.
* @retval vec Vector of strings. NULL terminated. Free after use
* @retval NULL Error *
*/
char **
clicon_sepsplit (char *string,
char *delim,
int *nvec,
const char *label)
clicon_strsep(char *string,
char *delim,
int *nvec0)
{
int idx;
size_t siz;
char *s, *s0;
char **vec, *vecp;
*nvec = 0;
s0 = s = chunkdup (string, strlen(string)+1, __FUNCTION__);
while (strsep(&s, delim))
(*nvec)++;
unchunk (s0);
siz = ((*nvec +1) * sizeof (char *)) + strlen(string) + 1;
vec = (char **) chunk (siz, label);
if (!vec) {
return NULL;
}
bzero (vec, siz);
vecp = (char *)&vec[*nvec +1];
bcopy (string, vecp, strlen (string));
for (idx = 0; idx < *nvec; idx++) {
vec[idx] = vecp;
strsep (&vecp, delim);
}
return vec;
}
/*! Split string into a vector based on a string delimiter
*
* The given string is split into a vector where the delimited by the
* the full delimiter string. The matched delimiters are not part of the
* resulting vector.
*
* See also clicon_sepsplit() which is similar
*
* The vector returned is one single memory chunk that must be unchunked
* by the caller
*
* @param[in] string String to be split
* @param[in] delim String of delimiter characters
* @param[out] nvec Number of entries in returned vector
* @param[in] label Chunk label for returned vector
* @retval vec Vector of strings. Free with unchunk
* @retval NULL Error
* @see clicon_sepsplit Operates on individual character delimiters rather
* than full string delimiter.
*/
char **
clicon_strsplit (char *string,
char *delim,
int *nvec,
const char *label)
{
int idx;
char **vec = NULL;
char *ptr;
char *p;
int nvec = 1;
int i;
size_t siz;
char *s;
char **vec, *vecp;
*nvec = 1;
s = string;
while ((s = strstr(s, delim))) {
s += strlen(delim);
(*nvec)++;
char *d;
if ((s = string)==NULL)
goto done;
while (*s){
if ((d = index(delim, *s)) != NULL)
nvec++;
s++;
}
siz = ((*nvec +1) * sizeof (char *)) + strlen(string) + 1;
vec = (char **) chunk (siz, label);
if (!vec) {
return NULL;
}
bzero (vec, siz);
vecp = (char *)&vec[*nvec +1];
bcopy (string, vecp, strlen (string));
s = vecp;
for (idx = 0; idx < *nvec; idx++) {
vec[idx] = s;
if ((s = strstr(s, delim)) != NULL) {
*s = '\0';
s += strlen(delim);
}
}
/* alloc vector and append copy of string */
siz = (nvec+1)* sizeof(char*) + strlen(string)+1;
if ((vec = (char**)malloc(siz)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(vec, 0, siz);
ptr = (char*)vec + (nvec+1)* sizeof(char*); /* this is where ptr starts */
strncpy(ptr, string, strlen(string)+1);
i = 0;
while ((p = strsep(&ptr, delim)) != NULL)
vec[i++] = p;
*nvec0 = nvec;
done:
return vec;
}
/*! Concatenate elements of a string array into a string.
* An optional delimiter string can be specified which will be inserted betwen
* each element.
* @param[in] label Chunk label for returned vector
* @retval str Joined string. Free with unchunk()
* @retval str Joined string. Free after use.
* @retval NULL Failure
*/
char *
clicon_strjoin (int argc,
char **argv,
char *delim,
const char *label)
clicon_strjoin(int argc,
char **argv,
char *delim)
{
int i;
int len;
char *str;
len = 0;
for (i = 0; i < argc; i++)
len += strlen(argv[i]);
if (delim)
len += (strlen(delim) * argc);
len += 1; /* '\0' */
if ((str = chunk (len, label)) == NULL)
return NULL;
memset (str, '\0', len);
for (i = 0; i < argc; i++) {
if (i != 0)
strncat (str, delim, len - strlen(str));
strncat (str, argv[i], len - strlen(str));
}
return str;
}
/*! Trim whitespace in beginning and end of string.
*
* @param[in] label Chunk label for returned vector
* @retval str Trimmed string. Free with unchunk()
* @retval NULL Failure
*/
char *
clicon_strtrim(char *str,
const char *label)
{
char *start, *end, *new;
start = str;
while (*start != '\0' && isspace(*start))
start++;
if (!strlen(start))
return (char *)chunkdup("\0", 1, label);
end = str + strlen(str) ;
while (end > str && isspace(*(end-1)))
end--;
if((new = chunkdup (start, end-start+1, label)))
new[end-start] = '\0';
return new;
}
/*! Given a string s, on format: a[b], separate it into two parts: a and b
* [] are separators.
* alterative use:
* a/b -> a and b (where sep = "/")
* @param[in] label Chunk label for returned vector
*/
int
clicon_sep(char *s,
const char sep[2],
const char *label,
char **a0,
char **b0)
{
char *a = NULL;
char *b = NULL;
char *ptr;
int i;
int len;
int retval = -1;
char *str;
ptr = s;
/* move forward to last char of element name */
while (*ptr && *ptr != sep[0] && *ptr != sep[1] )
ptr++;
/* Copy first element name */
len = ptr-s;
if ((a = chunkdup(s, len+1, label)) == NULL)
goto catch;
a[len] = '\0';
/* Do we have an extended format? */
if (*ptr == sep[0]) {
b = ++ptr;
/* move forward to end extension */
while (*ptr && *ptr != sep[1])
ptr++;
/* Copy extension */
len = ptr-b;
if ((b = chunkdup(b, len+1, label)) == NULL)
goto catch;
b[len] = '\0';
len = 0;
for (i = 0; i < argc; i++)
len += strlen(argv[i]);
if (delim)
len += (strlen(delim) * argc);
len += 1; /* '\0' */
if ((str = malloc(len)) == NULL)
return NULL;
memset (str, '\0', len);
for (i = 0; i < argc; i++) {
if (i != 0)
strncat (str, delim, len - strlen(str));
strncat (str, argv[i], len - strlen(str));
}
*a0 = a;
*b0 = b;
retval = 0;
catch:
return retval;
return str;
}
@ -302,82 +163,48 @@ clicon_strndup (const char *str,
}
#endif /* ! HAVE_STRNDUP */
/*! Match string against regexp.
*
* If a match pointer is given, the matching substring
* will be allocated 'match' will be pointing to it. The match string must
* be free:ed by the application.
* @retval -1 Failure
* @retval 0 No match
* @retval >0 Match: Length of matching substring
*/
/*
* Turn this on for uni-test programs
* Usage: clixon_string join
* Example compile:
gcc -g -o clixon_string -I. -I../clixon ./clixon_string.c -lclixon -lcligen
* Example run:
*/
#if 0 /* Test program */
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s <string>\n", argv0);
exit(0);
}
int
clicon_strmatch(const char *str,
const char *regexp,
char **match)
main(int argc, char **argv)
{
size_t len;
int status;
regex_t re;
char rxerr[128];
size_t nmatch = 1;
regmatch_t pmatch[1];
if (match)
*match = NULL;
if ((status = regcomp(&re, regexp, REG_EXTENDED)) != 0) {
regerror(status, &re, rxerr, sizeof(rxerr));
clicon_err(OE_REGEX, errno, "%s", rxerr);
return -1;
}
status = regexec(&re, str, nmatch, pmatch, 0);
regfree(&re);
if (status != 0)
return 0; /* No match */
len = pmatch[0].rm_eo - pmatch[0].rm_so;
/* If we've specified a match pointer, allocate and populate it. */
if (match) {
if ((*match = malloc(len + 1)) == NULL) {
clicon_err(OE_UNIX, errno, "Failed to allocate string");
return -1;
}
memset(*match, '\0', len + 1);
strncpy(*match, str + pmatch[0].rm_so, len);
}
return len;
}
/*! Substitute pattern in string.
* @retval str Malloc:ed string on success, use free to deallocate
* @retval NULL Failure.
*/
char *
clicon_strsub(char *str,
char *from,
char *to)
{
char **vec;
int nvec;
char *new;
char *retval = NULL;
char **vec;
char *str0;
char *str1;
int i;
if ((vec = clicon_strsplit(str, from, &nvec, __FUNCTION__)) == NULL) {
clicon_err(OE_UNIX, errno, "Failed to split string");
goto done;
if (argc != 2){
usage(argv[0]);
return 0;
}
if ((new = clicon_strjoin (nvec, vec, to, __FUNCTION__)) == NULL) {
clicon_err(OE_UNIX, errno, "Failed to split string");
goto done;
}
retval = strdup(new);
done:
unchunk_group(__FUNCTION__);
return retval;
str0 = argv[1];
if ((vec = clicon_strsep(str0, " \t", &nvec)) == NULL)
return -1;
fprintf(stderr, "nvec: %d\n", nvec);
for (i=0; i<nvec+1; i++)
fprintf(stderr, "vec[%d]: %s\n", i, vec[i]);
if ((str1 = clicon_strjoin(nvec, vec, " ")) == NULL)
return -1;
fprintf(stderr, "join: %s\n", str1);
free(vec);
free(str1);
return 0;
}
#endif /* Test program */

View file

@ -465,6 +465,12 @@ xml_childvec_set(cxobj *x,
return 0;
}
cxobj **
xml_childvec_get(cxobj *x)
{
return x->x_childvec;
}
/*! Create new xml node given a name and parent. Free it with xml_free().
*
* @param[in] name Name of new
@ -913,24 +919,26 @@ clicon_xml2cbuf(cbuf *cb,
cprintf(cb, "%s:", xml_namespace(cx));
cprintf(cb, "%s", xml_name(cx));
xc = NULL;
while ((xc = xml_child_each(cx, xc, -1)) != NULL) {
if (xml_type(xc) != CX_ATTR)
continue;
while ((xc = xml_child_each(cx, xc, CX_ATTR)) != NULL)
clicon_xml2cbuf(cb, xc, level+1, prettyprint);
/* Check for special case <a/> instead of <a></a> */
if (xml_body(cx)==NULL && xml_child_nr(cx)==0)
cprintf(cb, "/>");
else{
cprintf(cb, ">");
if (prettyprint && xml_body(cx)==NULL)
cprintf(cb, "\n");
xc = NULL;
while ((xc = xml_child_each(cx, xc, -1)) != NULL) {
if (xml_type(xc) == CX_ATTR)
continue;
else
clicon_xml2cbuf(cb, xc, level+1, prettyprint);
}
if (prettyprint && xml_body(cx)==NULL)
cprintf(cb, "%*s", level*XML_INDENT, "");
cprintf(cb, "</%s>", xml_name(cx));
}
cprintf(cb, ">");
if (prettyprint && xml_body(cx)==NULL)
cprintf(cb, "\n");
xc = NULL;
while ((xc = xml_child_each(cx, xc, -1)) != NULL) {
if (xml_type(xc) == CX_ATTR)
continue;
else
clicon_xml2cbuf(cb, xc, level+1, prettyprint);
}
if (prettyprint && xml_body(cx)==NULL)
cprintf(cb, "%*s", level*XML_INDENT, "");
cprintf(cb, "</%s>", xml_name(cx));
if (prettyprint)
cprintf(cb, "\n");
break;
@ -955,11 +963,11 @@ xml_parse(char *str,
return -1;
}
ya.ya_xparent = x_up;
ya.ya_skipspace = 1; /* remove all non-terminal bodies (strip pretty-print) */
if (clixon_xml_parsel_init(&ya) < 0)
goto done;
goto done;
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
goto done;
retval = 0;
done:
clixon_xml_parsel_exit(&ya);
@ -1079,7 +1087,6 @@ clicon_xml_parse_file(int fd,
* @endcode
* @see clicon_xml_parse_file
* @note you need to free the xml parse tree after use, using xml_free()
* Update: with yacc parser I dont think it changes,....
*/
int
clicon_xml_parse_str(char *str,
@ -1090,6 +1097,57 @@ clicon_xml_parse_str(char *str,
return xml_parse(str, *cxtop);
}
/*! Read XML definition from variable argument string and parse it into parse-tree.
*
* Utility function using stdarg instead of static string.
* @param[out] xml_top Top of XML parse tree. Will add extra top element called 'top'.
* you must free it after use, using xml_free()
* @param[in] format Pointer to string containing XML definition.
* @retval 0 OK
* @retval -1 Error with clicon_err called
*
* @code
* cxobj *cx = NULL;
* if (clicon_xml_parse(&cx, "<xml>%d</xml>", 22) < 0)
* err;
* xml_free(cx);
* @endcode
* @see clicon_xml_parse_str
* @note you need to free the xml parse tree after use, using xml_free()
*/
int
clicon_xml_parse(cxobj **cxtop,
char *format, ...)
{
int retval = -1;
va_list args;
char *str = NULL;
int len;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((str = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(str, 0, len);
va_start(args, format);
len = vsnprintf(str, len, format, args) + 1;
va_end(args);
if ((*cxtop = xml_new("top", NULL)) == NULL)
return -1;
if (xml_parse(str, *cxtop) < 0)
goto done;
retval = 0;
done:
if (str)
free(str);
return retval;
}
/*! Copy single xml node without copying children
*/
static int
@ -1402,3 +1460,54 @@ xml_body_uint32(cxobj *xb,
cv_free(cv);
return 0;
}
/*! Map xml operation from string to enumeration
* @param[in] xn XML node
* @param[out] op "operation" attribute may change operation
*/
int
xml_operation(char *opstr,
enum operation_type *op)
{
if (strcmp("merge", opstr) == 0)
*op = OP_MERGE;
else if (strcmp("replace", opstr) == 0)
*op = OP_REPLACE;
else if (strcmp("create", opstr) == 0)
*op = OP_CREATE;
else if (strcmp("delete", opstr) == 0)
*op = OP_DELETE;
else if (strcmp("remove", opstr) == 0)
*op = OP_REMOVE;
else if (strcmp("none", opstr) == 0)
*op = OP_NONE;
else{
clicon_err(OE_XML, 0, "Bad-attribute operation: %s", opstr);
return -1;
}
return 0;
}
char *
xml_operation2str(enum operation_type op)
{
switch (op){
case OP_MERGE:
return "merge";
break;
case OP_REPLACE:
return "replace";
break;
case OP_CREATE:
return "create";
break;
case OP_DELETE:
return "delete";
break;
case OP_REMOVE:
return "remove";
break;
default:
return "none";
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,576 +0,0 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* XML database
* TODO: xmldb_del: or dbxml_put_xkey delete
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <fnmatch.h>
#include <stdint.h>
#include <assert.h>
#include <syslog.h>
#include <arpa/inet.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
#include "clixon_string.h"
#include "clixon_chunk.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_qdb.h"
#include "clixon_yang.h"
#include "clixon_handle.h"
#include "clixon_yang.h"
#include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_xml_parse.h"
#include "clixon_xml_db.h"
#include "clixon_xml_db_rpc.h"
/*! Make an rpc call to xmldb daemon
*/
static int
xmldb_rpc(clicon_handle h,
char *data,
size_t len,
char *retdata,
size_t *retlen
)
{
int retval = -1;
char *dst;
uint16_t port;
int s = -1;
struct sockaddr_in addr;
if ((dst = clicon_xmldb_addr(h)) == NULL){
clicon_err(OE_CFG, errno, "CLICON_XMLDB_ADDR option not set");
goto done;
}
if ((port = clicon_xmldb_port(h)) == 0){
clicon_err(OE_CFG, errno, "CLICON_XMLDB_PORT option not set");
goto done;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(addr.sin_family, dst, &addr.sin_addr) != 1)
goto done; /* Could check getaddrinfo */
if ((s = socket(addr.sin_family, SOCK_STREAM, 0)) < 0) {
clicon_err(OE_CFG, errno, "socket");
return -1;
}
if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) < 0){
clicon_err(OE_CFG, errno, "connecting socket inet4");
close(s);
goto done;
}
if (write(s, data, len) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
if ((*retlen = read(s, retdata, *retlen)) < 0){
clicon_err(OE_UNIX, errno, "write");
goto done;
}
retval = 0;
if (debug > 1)
fprintf(stderr, "%s: \"%s\"\n", __FUNCTION__, retdata);
done:
if (s != -1)
close(s);
return retval;
}
/*! Send put request to backend daemon
* @param[in] h CLICON handle
* @param[in] db running|candidate
* @retval 0
*/
int
xmldb_put_rpc(clicon_handle h,
char *db,
cxobj *xt,
enum operation_type op)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
char *opstr;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><put>");
cprintf(cb, "<target><%s/></target>", db);
if (op){
switch (op){
case OP_REPLACE:
opstr = "replace";
break;
case OP_MERGE:
opstr = "merge";
break;
case OP_NONE:
default:
opstr = "none";
break;
}
cprintf(cb, "<default-operation>%s</default-operation>", opstr);
}
cprintf(cb, "<config>");
if (clicon_xml2cbuf(cb, xt, 0, 1) < 0)
goto done;
cprintf(cb, "</config>");
cprintf(cb, "</put></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send put xkey request to backend daemon
* @param[in] h CLICON handle
* @param[in] db running|candidate
* @retval 0
*/
int
xmldb_put_xkey_rpc(clicon_handle h,
char *db,
char *xk,
char *val,
enum operation_type op)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
char *opstr;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><put-xkey>");
cprintf(cb, "<target><%s/></target>", db);
if (op){
switch (op){
case OP_REPLACE:
opstr = "replace";
break;
case OP_MERGE:
opstr = "merge";
break;
case OP_NONE:
default:
opstr = "none";
break;
}
cprintf(cb, "<default-operation>%s</default-operation>", opstr);
}
cprintf(cb, "<xkey>%s</xkey>", xk);
cprintf(cb, "<value>%s</value>", val);
cprintf(cb, "</put-xkey></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send get request to backend daemon
* @param[in] h CLICON handle
* @param[in] db running|candidate
* @retval 0
*/
int
xmldb_get_rpc(clicon_handle h,
char *db,
char *xpath,
cxobj **xtop,
cxobj ***xvec,
size_t *xlen)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
cxobj *xt=NULL;
cxobj *xc;
int i;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><get>");
cprintf(cb, "<source><%s/></source>", db);
if (xpath)
cprintf(cb, "<xpath>%s</xpath>", xpath);
cprintf(cb, "</get></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
if (clicon_xml_parse_str(rb, &xt) < 0)
goto done;
if (xvec){
i=0;
if ((*xvec = calloc(xml_child_nr(xt), sizeof(cxobj*))) ==NULL){
clicon_err(OE_UNIX, errno, "calloc");
goto done;
}
xc = NULL;
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
(*xvec)[i++] = xc;
}
*xlen = i;
*xtop = xt;
xt = NULL;
}
else{
if (xml_rootchild(xt, 0, xtop) < 0)
goto done;
xt = NULL;
}
retval = 0;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Copy database
* @param[in] h Clicon handle
* @param[in] from Source database copy
* @param[in] to Destination database
* @retval -1 Error
* @retval 0 OK
*/
int
xmldb_copy_rpc(clicon_handle h,
char *from,
char *to)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
cxobj *xt = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><copy>");
cprintf(cb, "<source><%s/></source>", from);
cprintf(cb, "<target><%s/></target>", to);
cprintf(cb, "</copy></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
if (clicon_xml_parse_str(rb, &xt) < 0)
goto done;
if (xpath_first(xt, "//ok"))
retval = 0;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Lock database
* @param[in] h Clicon handle
* @param[in] db Database
* @param[in] pid Process id
* @retval -1 Error
* @retval 0 OK
*/
int
xmldb_lock_rpc(clicon_handle h,
char *db,
int id)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
cxobj *xt = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><lock>");
cprintf(cb, "<target><%s/></target>", db);
cprintf(cb, "<id><%u/></id>", id);
cprintf(cb, "</lock></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
if (clicon_xml_parse_str(rb, &xt) < 0)
goto done;
if (xpath_first(xt, "//ok"))
retval = 0;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Unlock database
* @param[in] h Clicon handle
* @param[in] db Database
* @param[in] pid Process id
* @retval -1 Error
* @retval 0 OK
*/
int
xmldb_unlock_rpc(clicon_handle h,
char *db,
int id)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
cxobj *xt = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><unlock>");
cprintf(cb, "<target><%s/></target>", db);
cprintf(cb, "<id><%u/></id>", id);
cprintf(cb, "</unlock></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
if (clicon_xml_parse_str(rb, &xt) < 0)
goto done;
if (xpath_first(xt, "//ok"))
retval = 0;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Check if database is locked
* @param[in] h Clicon handle
* @param[in] db Database
* @retval -1 Error
* @retval pid Process id if locked
*/
int
xmldb_islocked_rpc(clicon_handle h,
char *db)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
cxobj *xt = NULL;
cxobj *x;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><islocked>");
cprintf(cb, "<target><%s/></target>", db);
cprintf(cb, "</islocked></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
if (clicon_xml_parse_str(rb, &xt) < 0)
goto done;
if (xpath_first(xt, "//unlocked"))
retval = 0;
else
if ((x=xpath_first(xt, "//locked")) != NULL)
retval = atoi(xml_body(x));
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send exists request to backend daemon
* @param[in] h CLICON handle
* @param[in] db running|candidate
* @retval -1 Error
* @retval 0 No it does not exist
* @retval 1 Yes it exists
*/
int
xmldb_exists_rpc(clicon_handle h,
char *db)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
cxobj *xt = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><exists>");
cprintf(cb, "<target><%s/></target>", db);
cprintf(cb, "</exists></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
if (clicon_xml_parse_str(rb, &xt) < 0)
goto done;
if (xpath_first(xt, "//ok"))
retval = 1;
else
retval = 0;
done:
if (xt)
xml_free(xt);
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send delete request to backend daemon
* @param[in] h CLICON handle
* @param[in] db running|candidate
* @retval 0
*/
int
xmldb_delete_rpc(clicon_handle h,
char *db)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><delete>");
cprintf(cb, "<target><%s/></target>", db);
cprintf(cb, "</delete></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send init request to backend daemon
* @param[in] h CLICON handle
* @param[in] db running|candidate
* @retval 0
*/
int
xmldb_init_rpc(clicon_handle h,
char *db)
{
int retval = -1;
cbuf *cb = NULL;
char retbuf[BUFSIZ];
char *rb = retbuf;
size_t retlen = sizeof(retbuf);
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc><init>");
cprintf(cb, "<target><%s/></target>", db);
cprintf(cb, "</init></rpc>]]>]]>");
if (xmldb_rpc(h,
cbuf_get(cb),
cbuf_len(cb)+1,
rb, &retlen) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}

View file

@ -197,23 +197,26 @@ xml2txt(FILE *f, cxobj *x, int level)
* @param[in] x XML Parse-tree (to translate)
* @param[in] prepend0 Print this text in front of all commands.
* @param[in] gt option to steer cli syntax
* @param[in] label Memory chunk allocation label
*/
int
xml2cli(FILE *f,
cxobj *x,
char *prepend0,
enum genmodel_type gt,
const char *label)
enum genmodel_type gt)
{
int retval = -1;
cxobj *xe = NULL;
char *term;
char *prepend;
int bool;
int nr;
int i;
cbuf *cbpre;
/* Create prepend variable string */
if ((cbpre = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
nr = xml_child_nr(x);
if (!nr){
if (xml_type(x) == CX_BODY)
@ -226,10 +229,8 @@ xml2cli(FILE *f,
retval = 0;
goto done;
}
prepend = "";
if (prepend0)
if ((prepend = chunk_sprintf(label, "%s%s", prepend, prepend0)) == NULL)
goto done;
cprintf(cbpre, "%s", prepend0);
/* bool determines when to print a variable keyword:
!leaf T for all (ie parameter)
index GT_NONE F
@ -241,12 +242,11 @@ xml2cli(FILE *f,
*/
bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS && !xml_index(x));
// bool = (!x->xn_index || gt == GT_ALL);
if (bool &&
(prepend = chunk_sprintf(label, "%s%s%s",
prepend,
strlen(prepend)?" ":"",
xml_name(x))) == NULL)
goto done;
if (bool){
if (cbuf_len(cbpre))
cprintf(cbpre, " ");
cprintf(cbpre, "%s", xml_name(x));
}
xe = NULL;
/* First child is unique, then add that, before looping. */
i = 0;
@ -255,23 +255,19 @@ xml2cli(FILE *f,
if (xml_index(xe) && i < nr-1)
;
else
if (xml2cli(f, xe, prepend, gt, label) < 0)
if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
goto done;
if (xml_index(xe)){ /* assume index is first, otherwise need one more while */
if (gt ==GT_ALL && (prepend = chunk_sprintf(label, "%s %s",
prepend,
xml_name(xe))) == NULL)
goto done;
if ((prepend = chunk_sprintf(label, "%s %s",
prepend,
xml_value(xml_child_i(xe, 0)))) == NULL)
goto done;
if (gt == GT_ALL)
cprintf(cbpre, " %s", xml_name(xe));
cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0)));
}
i++;
}
retval = 0;
done:
if (cbpre)
cbuf_free(cbpre);
return retval;
}
@ -292,6 +288,7 @@ xml_yang_validate(cxobj *xt,
yang_stmt *yc;
int i;
yang_stmt *ys;
char *body;
/* if not given by argument (overide) use default link */
ys = ys0?ys0:xml_spec(xt);
@ -321,17 +318,19 @@ xml_yang_validate(cxobj *xt,
/* In the union case, value is parsed as generic REST type,
* needs to be reparsed when concrete type is selected
*/
if (cv_parse(xml_body(xt), cv) <0){
clicon_err(OE_UNIX, errno, "cv_parse");
goto done;
}
if ((ys_cv_validate(cv, ys, &reason)) != 1){
clicon_err(OE_DB, 0,
"validation of %s failed %s",
ys->ys_argument, reason?reason:"");
if (reason)
free(reason);
goto done;
if ((body = xml_body(xt)) != NULL){
if (cv_parse(body, cv) <0){
clicon_err(OE_UNIX, errno, "cv_parse");
goto done;
}
if ((ys_cv_validate(cv, ys, &reason)) != 1){
clicon_err(OE_DB, 0,
"validation of %s failed %s",
ys->ys_argument, reason?reason:"");
if (reason)
free(reason);
goto done;
}
}
break;
default:

View file

@ -46,6 +46,7 @@ struct xml_parse_yacc_arg{
cxobj *ya_xelement; /* xml active element */
cxobj *ya_xparent; /* xml parent element*/
int ya_skipspace; /* If set, remove all non-terminal bodies (strip prettyr-print) */
};
extern char *clixon_xml_parsetext;

View file

@ -141,11 +141,7 @@ int
clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya)
{
yy_delete_buffer(ya->ya_lexbuf);
#if defined(YY_FLEX_SUBMINOR_VERSION) && YY_FLEX_SUBMINOR_VERSION >= 9
clixon_xml_parselex_destroy(); /* modern */
#else
yy_init = 1; /* This does not quite free all buffers */
#endif
return 0;
}

View file

@ -100,36 +100,22 @@ xml_attr_new(struct xml_parse_yacc_arg *ya,
there may also be some leakage here on NULL return
*/
static int
xml_parse_content(struct xml_parse_yacc_arg *ya, char *str)
xml_parse_content(struct xml_parse_yacc_arg *ya,
char *str)
{
int sz;
char s0;
cxobj *xn = ya->ya_xelement;
cxobj *xp = ya->ya_xparent;
int retval = -1;
ya->ya_xelement = NULL; /* init */
s0 = str[0];
if (xn != NULL){
sz = strlen(xml_value(xn));
if (s0 == ' ' || s0 == '\n' || s0 == '\t'){
str[0] = ' ';
if (xml_value(xn)[sz-1] == ' ')
goto ok;
}
}
else{
if (s0 == ' ' || s0 == '\n' || s0 == '\t')
goto ok;
if (xn == NULL){
if ((xn = xml_new("body", xp)) == NULL)
goto done;
xml_type_set(xn, CX_BODY);
sz = 0;
}
if (xml_value_append(xn, str)==NULL)
goto done;
ya->ya_xelement = xn;
ok:
retval = 0;
done:
return retval;
@ -147,7 +133,6 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, char *ver)
return 0;
}
static int
xml_parse_id(struct xml_parse_yacc_arg *ya, char *name, char *namespace)
{
@ -193,18 +178,31 @@ xml_parse_endslash_post(struct xml_parse_yacc_arg *ya)
static int
xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
{
int retval = -1;
int retval = -1;
cxobj *x = ya->ya_xelement;
cxobj *xc;
if (strcmp(xml_name(ya->ya_xelement), name)){
if (strcmp(xml_name(x), name)){
clicon_err(OE_XML, 0, "Sanity check failed: %s vs %s",
xml_name(ya->ya_xelement), name);
xml_name(x), name);
goto done;
}
if (xml_namespace(ya->ya_xelement)!=NULL){
if (xml_namespace(x)!=NULL){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s\n",
xml_namespace(ya->ya_xelement), xml_name(ya->ya_xelement), name);
xml_namespace(x), xml_name(x), name);
goto done;
}
/* remove all non-terminal bodies (strip pretty-print) */
if (ya->ya_skipspace){
if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
;
else{
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL)
xml_value_set(xc, ""); /* XXX remove */
}
}
retval = 0;
done:
free(name);
@ -214,25 +212,37 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
static int
xml_parse_bslash2(struct xml_parse_yacc_arg *ya, char *namespace, char *name)
{
int retval = -1;
int retval = -1;
cxobj *x = ya->ya_xelement;
cxobj *xc;
if (strcmp(xml_name(ya->ya_xelement), name)){
if (strcmp(xml_name(x), name)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n",
xml_namespace(ya->ya_xelement),
xml_name(ya->ya_xelement),
xml_namespace(x),
xml_name(x),
namespace,
name);
goto done;
}
if (xml_namespace(ya->ya_xelement)==NULL ||
strcmp(xml_namespace(ya->ya_xelement), namespace)){
if (xml_namespace(x)==NULL ||
strcmp(xml_namespace(x), namespace)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n",
xml_namespace(ya->ya_xelement),
xml_name(ya->ya_xelement),
xml_namespace(x),
xml_name(x),
namespace,
name);
goto done;
}
/* remove all non-terminal bodies (strip pretty-print) */
if (ya->ya_skipspace){
if (xml_child_nr(x) == 1 && (xml_type(xml_child_i(x, 0))==CX_BODY))
;
else{
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL)
xml_value_set(xc, ""); /* XXX remove */
}
}
retval = 0;
done:
free(name);
@ -300,7 +310,7 @@ emnt : '<' id attrs emnt1
;
id : NAME { if (xml_parse_id(_YA, $1, NULL) < 0) YYABORT;
clicon_debug(3, "id -> NAME");}
clicon_debug(3, "id -> NAME %s", $1);}
| NAME ':' NAME { if (xml_parse_id(_YA, $3, $1) < 0) YYABORT;
clicon_debug(3, "id -> NAME : NAME");}
;
@ -314,8 +324,8 @@ emnt1 : ESLASH {_YA->ya_xelement = NULL;
;
etg : BSLASH NAME '>'
{ if (xml_parse_bslash1(_YA, $2) < 0) YYABORT;
clicon_debug(3, "etg -> < </ NAME >"); }
{ clicon_debug(3, "etg -> < </ NAME %s>", $2); if (xml_parse_bslash1(_YA, $2) < 0) YYABORT; }
| BSLASH NAME ':' NAME '>'
{ if (xml_parse_bslash2(_YA, $2, $4) < 0) YYABORT;
clicon_debug(3, "etg -> < </ NAME:NAME >"); }
@ -328,7 +338,7 @@ list : list content { clicon_debug(3, "list -> list content"); }
content : emnt { clicon_debug(3, "content -> emnt"); }
| comment { clicon_debug(3, "content -> comment"); }
| CHAR { if (xml_parse_content(_YA, $1) < 0) YYABORT;
clicon_debug(3, "content -> CHAR", $1); }
clicon_debug(3, "content -> CHAR %s", $1); }
| { clicon_debug(3, "content -> "); }
;

View file

@ -199,7 +199,7 @@ xpath_parse_predicate(struct xpath_element *xe,
int len;
len = strlen(pred);
for (i=len-1; i>=0; i--){ /* -2 since we search for ][ */
for (i=len-1; i>=0; i--){ /* -1 since we search for ][ */
s = &pred[i];
if (i==0 ||
(*(s)==']' && *(s+1)=='[')){
@ -358,12 +358,23 @@ xpath_parse(char *xpath,
else if (strncmp(s,"descendant-or-self::", strlen("descendant-or-self::"))==0){
xpath_element_new(A_DESCENDANT_OR_SELF, s+strlen("descendant-or-self::"), &xpnext);
}
#if 1 /* Problems with .[userid=1321] */
else if (strncmp(s,".", strlen("."))==0)
xpath_element_new(A_SELF, s+strlen("."), &xpnext);
#else
else if (strncmp(s,".", strlen(s))==0) /* abbreviatedstep */
xpath_element_new(A_SELF, NULL, &xpnext);
#endif
else if (strncmp(s,"self::", strlen("self::"))==0)
xpath_element_new(A_SELF, s+strlen("self::"), &xpnext);
#if 1
else if (strncmp(s,"..", strlen(".."))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, s+strlen(".."), &xpnext);
#else
else if (strncmp(s,"..", strlen(s))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, NULL, &xpnext);
#endif
else if (strncmp(s,"parent::", strlen("parent::"))==0)
xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext);
else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0)
@ -552,12 +563,6 @@ xpath_expr(char *predicate_expression,
* @param[in] flags if != 0, only match xml nodes matching flags
* @param[out] vec2 Result XML node vector
* @param[out] vec2len Length of result vector.
* XXX: Kommer in i funktionen med vec0, resultatet appendas i vec1
* vec0 --> vec
* Det är nog bra om vec0 inte ändras, är input parameter
* Vid utgång ska vec1 innehålla resultatet.
* Internt ?
* XXX: hantering av (input)vec0-->vec-->vec2-->vec1 (resultat)
*/
static int
xpath_find(struct xpath_element *xe,
@ -767,6 +772,7 @@ xpath_choice(cxobj *xtop,
cxobj **vec0 = NULL;
size_t vec0len = 0;
if ((s0 = strdup(xpath0)) == NULL){
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
goto done;
@ -926,8 +932,8 @@ xpath_each(cxobj *cxtop,
/*! A restricted xpath that returns a vector of matches
*
* See xpath1() on details for subset.
* @param[in] cxtop xml-tree where to search
* See xpath1() on details for subset
. * @param[in] cxtop xml-tree where to search
* @param[in] xpath string with XPATH syntax
* @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value
@ -945,7 +951,7 @@ xpath_each(cxobj *cxtop,
* }
* free(vec);
* @endcode
* Note that although the returned vector must be freed after use, the returned xml
* @Note that although the returned vector must be freed after use, the returned xml
* trees need not be.
* @see also xpath_first, xpath_each.
*/
@ -987,7 +993,27 @@ xpath_vec(cxobj *cxtop,
}
/* A restricted xpath that returns a vector of matches (only nodes marked with flags)
* @param[in] cxtop xml-tree where to search
* @param[in] xpath string with XPATH syntax
* @param[in] flags Set of flags that return nodes must match (0 if all)
* @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value
* @retval 0 OK
* @retval -1 error.
* @code
* cxobj **vec;
* size_t veclen;
* if (xpath_vec_flag(cxtop, "//symbol/foo", XML_FLAG_ADD, &vec, &veclen) < 0)
* goto err;
* for (i=0; i<veclen; i++){
* xn = vec[i];
* ...
* }
* free(vec);
* @endcode
* @Note that although the returned vector must be freed after use, the returned xml
* trees need not be.
* @see also xpath_vec This is a specialized version.
*/
int
xpath_vec_flag(cxobj *cxtop,

View file

@ -1676,37 +1676,6 @@ yang_xpath_vec(yang_node *yn,
return yret;
}
/* Alternative to clicon_strsplit using malloc. Note delim can only be one char
* Free return value after use
*/
static char **
clicon_strsplit_malloc(char *string,
char *delim,
int *nvec0)
{
char **vec = NULL;
char *ptr;
char *p;
int nvec = 1;
int i;
for (i=0; i<strlen(string); i++)
if (string[i]==*delim)
nvec++;
/* alloc vector and append copy of string */
if ((vec = (char**)malloc(nvec* sizeof(char*) + strlen(string)+1)) == NULL){
clicon_err(OE_YANG, errno, "malloc");
goto err;
}
ptr = (char*)vec + nvec* sizeof(char*); /* this is where ptr starts */
strncpy(ptr, string, strlen(string)+1);
i = 0;
while ((p = strsep(&ptr, delim)) != NULL)
vec[i++] = p;
*nvec0 = nvec;
err:
return vec;
}
/*! Given an absolute xpath (eg /a/b/c) find matching yang specification
* @param[in] yn Yang node
@ -1725,12 +1694,10 @@ yang_xpath_abs(yang_node *yn,
char *id;
char *prefix = NULL;
if ((vec = clicon_strsplit_malloc(xpath, "/", &nvec)) == NULL){
clicon_err(OE_YANG, errno, "%s: strsplit", __FUNCTION__);
if ((vec = clicon_strsep(xpath, "/", &nvec)) == NULL){
clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__);
return NULL;
}
/* Assume path looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){
clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s",
@ -1807,7 +1774,7 @@ yang_xpath(yang_node *yn,
/* check absolute path */
if (xpath[0] == '/')
return yang_xpath_abs(yn, xpath);
if ((vec = clicon_strsplit_malloc(xpath, "/", &nvec)) == NULL)
if ((vec = clicon_strsep(xpath, "/", &nvec)) == NULL)
goto err;
ys = yang_xpath_vec((yang_node*)yn, vec, nvec);
err:
@ -1986,16 +1953,14 @@ cvec *
yang_arg2cvec(yang_stmt *ys,
char *delim)
{
char **vec;
char **vec = NULL;
int i;
int nvec;
cvec *cvv = NULL;
cg_var *cv;
if ((vec = clicon_strsplit(ys->ys_argument, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_YANG, errno, "clicon_strsplit");
if ((vec = clicon_strsep(ys->ys_argument, " ", &nvec)) == NULL)
goto done;
}
if ((cvv = cvec_new(nvec)) == NULL){
clicon_err(OE_YANG, errno, "cvec_new");
goto done;
@ -2010,7 +1975,8 @@ yang_arg2cvec(yang_stmt *ys,
}
}
done:
unchunk_group(__FUNCTION__);
if (vec)
free(vec);
return cvv;
}

View file

@ -248,11 +248,7 @@ int
yang_scan_exit(struct clicon_yang_yacc_arg *yy)
{
yy_delete_buffer(yy->yy_lexbuf);
#if defined(YY_FLEX_SUBMINOR_VERSION) && YY_FLEX_SUBMINOR_VERSION >= 9
clixon_yang_parselex_destroy(); /* modern */
#else
yy_init = 1; /* This does not quite free all buffers */
#endif
return 0;
}

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# include err() and new() functions
. ./lib.sh

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
testnr=0
testnname=
@ -32,7 +32,7 @@ expectfn(){
fi
# grep extended grep
match=`echo "$ret" | grep -Eo "$expect"`
# echo "ret:$ret"
# echo "ret:<$ret>"
# echo "expect:$expect"
# echo "match:$match"
if [ -z "$match" ]; then
@ -61,3 +61,21 @@ EOF
fi
}
# clicon_cli tester. First arg is command and second is expected outcome
expectwait(){
cmd=$1
input=$2
expect=$3
wait=$4
# Do while read stuff
sleep 10|cat <(echo $input) -| $cmd | while [ 1 ] ; do
read ret
match=$(echo "$ret" | grep -Eo "$expect");
if [ -z "$match" ]; then
err "\nExpected:\t\"$expect\"\nGot:\t\"$ret\""
fi
break
done
}

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# Test1: backend and cli basic functionality
# Start backend server
# Add an ethernet interface and an address
@ -10,6 +10,10 @@
# include err() and new() functions
. ./lib.sh
# For memcheck
#clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli"
clixon_cli=clixon_cli
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $clixon_cf
@ -18,26 +22,72 @@ if [ $? -ne 0 ]; then
fi
new "start backend"
# start new backend
sudo clixon_backend -If $clixon_cf -x 0 # -x 1 with xmldb proxy
sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then
err
fi
new "cli configure top"
expectfn "$clixon_cli -1f $clixon_cf set interfaces" ""
new "cli show configuration top"
expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces$"
new "cli configure delete top"
expectfn "$clixon_cli -1f $clixon_cf delete interfaces" ""
new "cli show configuration delete top"
expectfn "$clixon_cli -1f $clixon_cf show conf cli" ""
new "cli configure"
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0" ""
new "cli show configuration"
expectfn "clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth0
expectfn "$clixon_cli -1f $clixon_cf show conf cli" "^interfaces interface name eth0
interfaces interface enabled true$"
new "cli failed validate"
expectfn "clixon_cli -1f $clixon_cf -l o validate" "Validate failed"
expectfn "$clixon_cli -1f $clixon_cf -l o validate" "Missing mandatory variable"
new "cli configure more"
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 ipv4 address 1.2.3.4 prefix-length 24" ""
expectfn "clixon_cli -1f $clixon_cf set interfaces interface eth0 type bgp" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 ipv4 address 1.2.3.4 prefix-length 24" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 description mydesc" ""
expectfn "$clixon_cli -1f $clixon_cf set interfaces interface eth0 type bgp" ""
new "cli show xpath description"
expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" "<description>mydesc</description>"
new "cli delete description"
expectfn "$clixon_cli -1f $clixon_cf -l o delete interfaces interface eth0 description mydesc"
new "cli show xpath no description"
expectfn "$clixon_cli -1f $clixon_cf -l o show xpath /interfaces/interface/description" ""
new "cli success validate"
expectfn "$clixon_cli -1f $clixon_cf -l o validate" ""
new "cli commit"
expectfn "clixon_cli -1f $clixon_cf -l o commit" ""
expectfn "$clixon_cli -1f $clixon_cf -l o commit" ""
new "cli save"
expectfn "$clixon_cli -1f $clixon_cf -l o save /tmp/foo" ""
new "cli delete all"
expectfn "$clixon_cli -1f $clixon_cf -l o delete all" ""
new "cli load"
expectfn "$clixon_cli -1f $clixon_cf -l o load /tmp/foo" ""
new "cli check load"
expectfn "$clixon_cli -1f $clixon_cf -l o show conf cli" "^interfaces interface name eth0
interfaces interface enabled true$"
new "cli debug"
expectfn "$clixon_cli -1f $clixon_cf -l o debug level 1" ""
# How to test this?
expectfn "$clixon_cli -1f $clixon_cf -l o debug level 0" ""
new "cli downcall"
expectfn "$clixon_cli -1f $clixon_cf -l o downcall \"This is a test =====\"" "^\"This is a test =====\"$"
new "Kill backend"
# Check if still alive

View file

@ -1,15 +1,13 @@
#!/bin/sh
# Test1: backend and cli basic functionality
# Start backend server
# Add an ethernet interface and an address
# Show configuration
# Validate without a mandatory type
# Set the mandatory type
# Commit
#!/bin/bash
# Test2: backend and netconf basic functionality
# include err() and new() functions
. ./lib.sh
# For memcheck
# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf"
clixon_netconf=clixon_netconf
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $clixon_cf
@ -18,15 +16,87 @@ if [ $? -ne 0 ]; then
fi
new "start backend"
# start new backend
sudo clixon_backend -If $clixon_cf -x 0 # -x 1 with xmldb proxy
sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then
err
fi
new "netconf show config"
expecteof "clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "<rpc-reply><data></data></rpc-reply>]]>]]>"
new "netconf get empty config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc message-id=\"101\"><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply message-id=\"101\"><data/></rpc-reply>]]>]]>$"
new "netconf lock"
expecteof "clixon_netconf -qf $clixon_cf" "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]>" "<rpc-reply><ok/></rpc-reply>]]>]]>"
new "netconf edit config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth0</name></interface><interface><name>eth1</name><enabled>true</enabled><ipv4><address><ip>9.2.3.4</ip><prefix-length>24</prefix-length></address></ipv4></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/interfaces/interface[name=eth1]/enabled\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf validate missing type"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error>"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get empty config2"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
new "netconf edit config eth1"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth1</name><type>eth</type></interface></interfaces></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf validate"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit config replace"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><edit-config><target><candidate/></target><config><interfaces><interface><name>eth2</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation> </edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get replaced config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface><interface><name>eth2</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf edit config create"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth3</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf edit config create 2nd"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="create"><name>eth3</name><type>eth</type></interface></interfaces></config><default-operation>merge</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><rpc-error>"
new "netconf edit config delete"
expecteof "$clixon_netconf -qf $clixon_cf" '<rpc><edit-config><target><candidate/></target><config><interfaces><interface operation="delete"><name>eth3</name><type>eth</type></interface></interfaces></config><default-operation>none</default-operation> </edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get delete config"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface><interface><name>eth2</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf discard-changes"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><discard-changes/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf lock/unlock"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]><rpc><unlock><target><candidate/></target></unlock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf lock/lock"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf lock"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "close-session"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><close-session/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "kill-session"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "copy startup"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><copy-config><target><startup/></target><source><candidate/></source></copy-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get startup"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><interfaces><interface><name>eth1</name><type>eth</type><enabled>true</enabled></interface></interfaces></config></data></rpc-reply>]]>]]>$"
new "netconf delete startup"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf check empty startup"
expecteof "$clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><startup/></source></get-config></rpc>]]>]]>" "^<rpc-reply><data/></rpc-reply>]]>]]>$"
new "netconf subscription"
expectwait "$clixon_netconf -qf $clixon_cf" "<rpc><create-subscription><stream>ROUTING</stream></create-subscription></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]><notification><event>Routing notification</event></notification>]]>]]>$" 30
new "Kill backend"
# Check if still alive
@ -39,4 +109,3 @@ sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then
err "kill backend"
fi

72
test/test3.sh Executable file
View file

@ -0,0 +1,72 @@
#!/bin/bash
# Test3: backend and restconf basic functionality
# include err() and new() functions
. ./lib.sh
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then
err
fi
new "start backend"
sudo clixon_backend -If $clixon_cf
if [ $? -ne 0 ]; then
err
fi
new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -Df /usr/local/etc/routing.conf # -D
sleep 1
new "restconf options"
expectfn "curl -i -s -X OPTIONS http://localhost/restconf/data" "Allow: OPTIONS,HEAD,GET,POST,PUT,DELETE"
new "restconf get empty config"
expectfn "curl -sG http://localhost/restconf/data" "^null $"
new "restconf put config"
expectfn 'curl -sX POST -d {"interfaces":{"interface":[{"name":"eth1","type":"eth","enabled":"true"},{"name":"eth0","type":"eth","enabled":"true"}]}} http://localhost/restconf/data' ""
new "restconf get config"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth1","type": "eth","enabled": "true"},{ "name": "eth0","type": "eth","enabled": "true"}\]}}
$'
new "restconf head"
expectfn "curl -s -I http://localhost/restconf/data" "Content-Type: application/yang.data\+json"
new "restconf POST config"
expectfn 'curl -sX POST -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
new "restconf DELETE config"
expectfn 'curl -sX DELETE http://localhost/restconf/data/interfaces/interface=eth0' ""
new "restconf get config"
expectfn "curl -sG http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth1","type": "eth","enabled": "true"},{ "name": "eth4","type": "eth","enabled": "true"}\]}}
$'
new "restconf PATCH config"
expectfn 'curl -sX PATCH -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth4' ""
new "restconf PUT"
expectfn 'curl -sX PUT -d {"type":"eth"} http://localhost/restconf/data/interfaces/interface=eth5' ""
new "Kill restconf daemon"
#sudo pkill -u www-data clixon_restconf
new "Kill backend"
# Check if still alive
pid=`pgrep clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then
err "kill backend"
fi

65
test/test4.sh Executable file
View file

@ -0,0 +1,65 @@
#!/bin/bash
# Test2: backend and netconf basic functionality
# include err() and new() functions
. ./lib.sh
# For memcheck
# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf"
clixon_netconf=clixon_netconf
cat <<EOF > /tmp/test.yang
module ietf-ip{
container x {
list y {
key "a b";
leaf a {
type string;
}
leaf b {
type string;
}
leaf c {
type string;
}
}
leaf d {
type empty;
}
}
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $clixon_cf -y /tmp/test
if [ $? -ne 0 ]; then
err
fi
new "start backend"
# start new backend
sudo clixon_backend -If $clixon_cf -y /tmp/test
if [ $? -ne 0 ]; then
err
fi
new "netconf edit config"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><edit-config><target><candidate/></target><config><x><y><a>1</a><b>2</b><c>5</c></y><d/></x></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf get config xpath"
expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "<rpc><get-config><source><candidate/></source><filter type=\"xpath\" select=\"/x/y[a=1][b=2]\"/></get-config></rpc>]]>]]>" "^<rpc-reply><data><config><x><y><a>1</a><b>2</b><c>5</c></y></x></config></data></rpc-reply>]]>]]>$"
new "Kill backend"
# Check if still alive
pid=`pgrep clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then
err "kill backend"
fi