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 ***** # ***** 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 - 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 enable. Eg, if backend_main is started with -CIr startup will be copied to
running. running.
- Added ".." as valid step in xpath - Added ".." as valid step in xpath
- Use restconf format for internal xmldb keys. Eg /a/b=3,4 - 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 - List keys with special characters RFC 3986 encoded.
- Added cli multiple callback and expand support. Use options
CLICON_CLIGEN_CALLBACK_SINGLE_ARG and CLICON_CLIGEN_EXPAND_SINGLE_ARG - Replaced cli expand functions with single to multiple args
to control these. This change is _not_ backward compatible
The multiple support for expand callbacks is enabled but not for callbacks This effects all calls to expand_dbvar() or user-defined
since this causes problems for legacy applications. expand callbacks
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 - Replaced cli callback functions with single arg to multiple args
for (eg cli_commitv). See clixon_cli_api.h for new names. 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 --with-cligen and --with-qdbm configure options
- Added union type check for non-cli (eg xml) input - Added union type check for non-cli (eg xml) input
- Empty yang type. Relaxed yang types for unions, eg two strings with different length. - 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) 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: only somewhat simplified:
Do commits in develop branch. When done, merge with master. Do commits in develop branch. When done, merge with master.
$ git checkout develop $ git checkout develop
Switch to branch develop Switch to branch develop
$ git add .. $ git add ..

View file

@ -40,7 +40,7 @@ LIBS = @LIBS@
SHELL = /bin/sh SHELL = /bin/sh
SUBDIRS = cli backend dbctrl netconf xmldb restconf SUBDIRS = cli backend dbctrl netconf restconf
.PHONY: all clean depend install $(SUBDIRS) .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 * Prototypes
*/ */
int backend_client_rm(clicon_handle h, struct client_entry *ce); 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); int from_client(int fd, void *arg);
#endif /* _BACKEND_CLIENT_H_ */ #endif /* _BACKEND_CLIENT_H_ */

View file

@ -126,6 +126,10 @@ generic_validate(yang_spec *yspec,
} }
/*! Common code of candidate_validate and candidate_commit /*! 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 static int
validate_common(clicon_handle h, validate_common(clicon_handle h,
@ -137,7 +141,6 @@ validate_common(clicon_handle h,
int i; int i;
cxobj *xn; cxobj *xn;
if ((yspec = clicon_dbspec_yang(h)) == NULL){ if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC"); clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done; goto done;
@ -160,7 +163,7 @@ validate_common(clicon_handle h,
&td->td_tcvec, /* changed: wanted values */ &td->td_tcvec, /* changed: wanted values */
&td->td_clen) < 0) &td->td_clen) < 0)
goto done; goto done;
if (debug) if (debug>1)
transaction_print(stderr, td); transaction_print(stderr, td);
/* Mark as changed in tree */ /* Mark as changed in tree */
for (i=0; i<td->td_dlen; i++){ /* Also down */ for (i=0; i<td->td_dlen; i++){ /* Also down */
@ -253,126 +256,145 @@ candidate_commit(clicon_handle h,
return retval; return retval;
} }
/*! Do a diff between candidate and running, then start a validate transaction /*! Commit changes from candidate to running
*
* @param[in] h Clicon handle * @param[in] h Clicon handle
* @param[in] candidate: The candidate database. The wanted backend state * @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 int
candidate_validate(clicon_handle h, from_client_commit(clicon_handle h,
char *candidate) int mypid,
cbuf *cbret)
{
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; int retval = -1;
transaction_data_t *td = NULL; 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 */ /* 1. Start transaction */
if ((td = transaction_new()) == NULL) if ((td = transaction_new()) == NULL)
goto done; goto done;
/* Common steps (with commit) */ /* Common steps (with commit) */
if (validate_common(h, candidate, td) < 0) if (validate_common(h, db, td) < 0){
goto done; 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; retval = 0;
done: done:
/* In case of failure, call plugin transaction termination callbacks */
if (retval < 0 && td) if (retval < 0 && td)
plugin_transaction_abort(h, td); plugin_transaction_abort(h, td);
if (td) if (td)
transaction_free(td); transaction_free(td);
return retval; 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 */ } /* from_client_validate */

View file

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

View file

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

View file

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

View file

@ -40,7 +40,6 @@
* Types * Types
*/ */
/*! Transaction data /*! Transaction data
* Clicon internal, presented as void* to app's callback in the 'transaction_data' * Clicon internal, presented as void* to app's callback in the 'transaction_data'
* type in clicon_backend_api.h * 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_reset_state(clicon_handle h, char *dbname);
int plugin_start_hooks(clicon_handle h, int argc, char **argv); 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); transaction_data_t * transaction_new(void);
int transaction_free(transaction_data_t *); int transaction_free(transaction_data_t *);

View file

@ -193,12 +193,12 @@ config_socket_init(clicon_handle h)
return 0; return 0;
} }
/* /*! Accept new socket client
* config_accept_client
* XXX: credentials not properly implemented * XXX: credentials not properly implemented
*/ */
int int
config_accept_client(int fd, void *arg) config_accept_client(int fd,
void *arg)
{ {
int retval = -1; int retval = -1;
clicon_handle h = (clicon_handle)arg; clicon_handle h = (clicon_handle)arg;
@ -218,7 +218,7 @@ config_accept_client(int fd, void *arg)
char *mem; char *mem;
int i; int i;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(2, "%s", __FUNCTION__);
len = sizeof(from); len = sizeof(from);
if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){ if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){
clicon_err(OE_UNIX, errno, "%s: accept", __FUNCTION__); 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 * 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; goto done;
retval = 0; retval = 0;
done: done:

View file

@ -133,6 +133,7 @@ backend_notify(clicon_handle h,
struct handle_subscription *hs; struct handle_subscription *hs;
int retval = -1; int retval = -1;
clicon_debug(2, "%s %s", __FUNCTION__, stream);
/* First thru all clients(sessions), and all subscriptions and find matches */ /* First thru all clients(sessions), and all subscriptions and find matches */
for (ce = backend_client_list(h); ce; ce = ce_next){ for (ce = backend_client_list(h); ce; ce = ce_next){
ce_next = 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 */ /* Then go thru all global (handle) subscriptions and find matches */
hs = NULL; hs = NULL;
while ((hs = subscription_each(h, hs)) != NULL){ while ((hs = subscription_each(h, hs)) != NULL){
if (hs->hs_format != MSG_NOTIFY_TXT) if (hs->hs_format != FORMAT_TEXT)
continue; continue;
if (strcmp(hs->hs_stream, stream)) if (strcmp(hs->hs_stream, stream))
continue; continue;
@ -239,7 +240,7 @@ backend_notify_xml(clicon_handle h,
/* Then go thru all global (handle) subscriptions and find matches */ /* Then go thru all global (handle) subscriptions and find matches */
hs = NULL; hs = NULL;
while ((hs = subscription_each(h, hs)) != NULL){ while ((hs = subscription_each(h, hs)) != NULL){
if (hs->hs_format != MSG_NOTIFY_XML) if (hs->hs_format != FORMAT_XML)
continue; continue;
if (strcmp(hs->hs_stream, stream)) if (strcmp(hs->hs_stream, stream))
continue; continue;
@ -257,7 +258,8 @@ backend_notify_xml(clicon_handle h,
} }
struct client_entry * 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 backend_handle *cb = handle(h);
struct client_entry *ce; struct client_entry *ce;
@ -411,3 +413,79 @@ subscription_each(clicon_handle h,
hs = cb->cb_subscription; hs = cb->cb_subscription;
return hs; 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 * 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. /*! Generic downcall registration.
* Enables any function to be called from (cli) frontend * 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 *subscription_each(clicon_handle h,
struct handle_subscription *hprev); 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_ */ #endif /* _CLIXON_BACKEND_HANDLE_H_ */

View file

@ -74,35 +74,6 @@
#include "cli_common.h" #include "cli_common.h"
/*! Initialize candidate database
* We have implemented these:
* shared - all users share a common candidate db
*/
int
init_candidate_db(clicon_handle h)
{
int retval = -1;
if (xmldb_exists(h, "running") != 1){
clicon_err(OE_FATAL, 0, "Running db does not exist");
goto err;
}
if (xmldb_exists(h, "candidate") != 1)
if (clicon_rpc_copy(h, "running", "candidate") < 0)
goto err;
retval = 0;
err:
return retval;
}
/*! Exit candidate db
* (private canddidates should be removed?)
*/
int
exit_candidate_db(clicon_handle h)
{
return 0;
}
/*! Register log notification stream /*! Register log notification stream
* @param[in] h Clicon handle * @param[in] h Clicon handle
@ -139,10 +110,10 @@ cli_notification_register(clicon_handle h,
if (status){ /* start */ if (status){ /* start */
if (s_exist!=-1){ if (s_exist!=-1){
clicon_err(OE_PLUGIN, 0, "%s: result log socket already exists", __FUNCTION__); clicon_err(OE_PLUGIN, 0, "Result log socket already exists");
goto done; goto done;
} }
if (clicon_rpc_subscription(h, status, stream, format, filter, &s) < 0) if (clicon_rpc_create_subscription(h, stream, filter, &s) < 0)
goto done; goto done;
if (cligen_regfd(s, fn, arg) < 0) if (cligen_regfd(s, fn, arg) < 0)
goto done; goto done;
@ -154,9 +125,10 @@ cli_notification_register(clicon_handle h,
cligen_unregfd(s_exist); cligen_unregfd(s_exist);
} }
hash_del(cdat, logname); hash_del(cdat, logname);
if (clicon_rpc_subscription(h, status, stream, format, filter, NULL) < 0) #if 0 /* cant turn off */
if (clicon_rpc_create_subscription(h, status, stream, format, filter, NULL) < 0)
goto done; goto done;
#endif
} }
retval = 0; retval = 0;
done: done:
@ -214,7 +186,7 @@ cli_signal_flush(clicon_handle h)
* @param[in] cvv Vector of cli string and instantiated variables * @param[in] cvv Vector of cli string and instantiated variables
* @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s"
* @param[in] op Operation to perform on database * @param[in] op Operation to perform on database
* Cvv will contain forst the complete cli string, and then a set of optional * Cvv will contain first the complete cli string, and then a set of optional
* instantiated variables. * instantiated variables.
* Example: * Example:
* cvv[0] = "set interfaces interface eth0 type bgp" * cvv[0] = "set interfaces interface eth0 type bgp"
@ -225,7 +197,7 @@ cli_signal_flush(clicon_handle h)
* @see cli_callback_generate where arg is generated * @see cli_callback_generate where arg is generated
*/ */
static int static int
cli_dbxmlv(clicon_handle h, cli_dbxml(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv, cvec *argv,
enum operation_type op) enum operation_type op)
@ -237,6 +209,7 @@ cli_dbxmlv(clicon_handle h,
cg_var *cval; cg_var *cval;
int len; int len;
cg_var *arg; cg_var *arg;
cbuf *cb = NULL;
if (cvec_len(argv) != 1){ if (cvec_len(argv) != 1){
clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__); clicon_err(OE_PLUGIN, 0, "%s: Requires one element to be xml key format string", __FUNCTION__);
@ -254,14 +227,24 @@ cli_dbxmlv(clicon_handle h,
goto done; goto done;
} }
} }
if (clicon_rpc_change(h, "candidate", op, xk, str) < 0) if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (str)
cprintf(cb, "<config>%s</config>", str);
else
cprintf(cb, "<config/>");
if (clicon_rpc_edit_config(h, "candidate", op, xk, cbuf_get(cb)) < 0)
goto done; goto done;
if (clicon_autocommit(h)) { if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h, "candidate", "running") < 0) if (clicon_rpc_commit(h) < 0)
goto done; goto done;
} }
retval = 0; retval = 0;
done: done:
if (cb)
cbuf_free(cb);
if (str) if (str)
free(str); free(str);
if (xk) if (xk)
@ -270,40 +253,54 @@ cli_dbxmlv(clicon_handle h,
} }
int int
cli_setv(clicon_handle h, cvec *cvv, cvec *argv) cli_set(clicon_handle h, cvec *cvv, cvec *argv)
{ {
int retval = 1; int retval = 1;
if (cli_dbxmlv(h, cvv, argv, OP_REPLACE) < 0) if (cli_dbxml(h, cvv, argv, OP_REPLACE) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
int cli_setv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_set(h, vars, argv);
}
int int
cli_mergev(clicon_handle h, cvec *cvv, cvec *argv) cli_merge(clicon_handle h, cvec *cvv, cvec *argv)
{ {
int retval = -1; int retval = -1;
if (cli_dbxmlv(h, cvv, argv, OP_MERGE) < 0) if (cli_dbxml(h, cvv, argv, OP_MERGE) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
int cli_mergev(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_merge(h, vars, argv);
}
int int
cli_delv(clicon_handle h, cvec *cvv, cvec *argv) cli_del(clicon_handle h, cvec *cvv, cvec *argv)
{ {
int retval = -1; int retval = -1;
if (cli_dbxmlv(h, cvv, argv, OP_REMOVE) < 0) if (cli_dbxml(h, cvv, argv, OP_REMOVE) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
int cli_delv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_del(h, vars, argv);
}
/*! Set debug level on CLI client (not backend daemon) /*! Set debug level on CLI client (not backend daemon)
* @param[in] h Clicon handle * @param[in] h Clicon handle
@ -313,7 +310,7 @@ cli_delv(clicon_handle h, cvec *cvv, cvec *argv)
* _or_ if a 'level' variable is present in vars use that value instead. * _or_ if a 'level' variable is present in vars use that value instead.
*/ */
int int
cli_debug_cliv(clicon_handle h, cli_debug_cli(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
@ -335,6 +332,10 @@ cli_debug_cliv(clicon_handle h,
done: done:
return retval; return retval;
} }
int cli_debug_cliv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_debug_cli(h, vars, argv);
}
/*! Set debug level on backend daemon (not CLI) /*! Set debug level on backend daemon (not CLI)
* @param[in] h Clicon handle * @param[in] h Clicon handle
@ -344,7 +345,7 @@ cli_debug_cliv(clicon_handle h,
* _or_ if a 'level' variable is present in vars use that value instead. * _or_ if a 'level' variable is present in vars use that value instead.
*/ */
int int
cli_debug_backendv(clicon_handle h, cli_debug_backend(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
@ -365,11 +366,15 @@ cli_debug_backendv(clicon_handle h,
done: done:
return retval; return retval;
} }
int cli_debug_backendv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_debug_backend(h, vars, argv);
}
/*! Set syntax mode /*! Set syntax mode
*/ */
int int
cli_set_modev(clicon_handle h, cli_set_mode(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
@ -386,12 +391,16 @@ cli_set_modev(clicon_handle h,
done: done:
return retval; return retval;
} }
int cli_set_modev(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_set_mode(h, vars, argv);
}
/*! Start bash from cli callback /*! Start bash from cli callback
* XXX Application specific?? * XXX Application specific??
*/ */
int int
cli_start_shellv(clicon_handle h, cli_start_shell(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
@ -443,31 +452,37 @@ cli_start_shellv(clicon_handle h,
return 0; return 0;
} }
int cli_start_shellv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_start_shell(h, vars, argv);
}
/*! Generic quit callback /*! Generic quit callback
*/ */
int int
cli_quitv(clicon_handle h, cli_quit(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
cli_set_exiting(h, 1); cli_set_exiting(h, 1);
return 0; return 0;
} }
int cli_quitv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_quit(h, vars, argv);
}
/*! Generic commit callback /*! Generic commit callback
* @param[in] argv No arguments expected * @param[in] argv No arguments expected
*/ */
int int
cli_commitv(clicon_handle h, cli_commit(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
int retval = -1; int retval = -1;
if ((retval = clicon_rpc_commit(h, if ((retval = clicon_rpc_commit(h)) < 0){ /* startup */
"candidate",
"running")) < 0){ /* startup */
cli_output(stderr, "Commit failed. Edit and try again or discard changes"); cli_output(stderr, "Commit failed. Edit and try again or discard changes");
goto done; goto done;
} }
@ -475,21 +490,30 @@ cli_commitv(clicon_handle h,
done: done:
return retval; return retval;
} }
int cli_commitv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_commit(h, vars, argv);
}
/*! Generic validate callback /*! Generic validate callback
*/ */
int int
cli_validatev(clicon_handle h, cli_validate(clicon_handle h,
cvec *vars, cvec *vars,
cvec *argv) cvec *argv)
{ {
int retval = -1; int retval = -1;
if ((retval = clicon_rpc_validate(h, "candidate")) < 0) if ((retval = clicon_rpc_validate(h, "candidate")) < 0)
clicon_err(OE_CFG, 0, "Validate failed. Edit and try again or discard changes"); goto done;
retval = 0;
done:
return retval; return retval;
} }
int cli_validatev(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_validate(h, vars, argv);
}
/*! Compare two dbs using XML. Write to file and run diff /*! Compare two dbs using XML. Write to file and run diff
*/ */
@ -558,7 +582,7 @@ compare_xmls(cxobj *xc1,
* @param[in] arg arg: 0 as xml, 1: as text * @param[in] arg arg: 0 as xml, 1: as text
*/ */
int int
compare_dbsv(clicon_handle h, compare_dbs(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
@ -575,9 +599,9 @@ compare_dbsv(clicon_handle h,
astext = cv_int32_get(cvec_i(argv, 0)); astext = cv_int32_get(cvec_i(argv, 0));
else else
astext = 0; astext = 0;
if (xmldb_get(h, "running", "/", &xc1, NULL, NULL) < 0) if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0)
goto done; goto done;
if (xmldb_get(h, "candidate", "/", &xc2, NULL, NULL) < 0) if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0)
goto done; goto done;
if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */ if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */
goto done; goto done;
@ -590,6 +614,10 @@ compare_dbsv(clicon_handle h,
return retval; return retval;
} }
int compare_dbsv(clicon_handle h, cvec *vars, cvec *argv)
{
return compare_dbs(h, vars, argv);
}
/*! Load a configuration file to candidate database /*! Load a configuration file to candidate database
* Utility function used by cligen spec file * Utility function used by cligen spec file
@ -606,21 +634,19 @@ compare_dbsv(clicon_handle h,
* @see save_config_file * @see save_config_file
*/ */
int int
load_config_filev(clicon_handle h, load_config_file(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
int ret = -1; int ret = -1;
struct stat st; struct stat st;
char **vecp; char *filename = NULL;
char *filename;
int replace; int replace;
cg_var *cv; cg_var *cv;
char *opstr; char *opstr;
char *varstr; char *varstr;
int fd = -1; int fd = -1;
cxobj *xt = NULL; cxobj *xt = NULL;
cxobj *xn;
cxobj *x; cxobj *x;
cbuf *cbxml; cbuf *cbxml;
@ -646,11 +672,7 @@ load_config_filev(clicon_handle h,
clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr); clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
goto done; goto done;
} }
if ((vecp = clicon_realpath(NULL, cv_string_get(cv), __FUNCTION__)) == NULL){ filename = cv_string_get(cv);
cli_output(stderr, "Failed to resolve filename\n");
goto done;
}
filename = vecp[0];
if (stat(filename, &st) < 0){ if (stat(filename, &st) < 0){
clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s", clicon_err(OE_UNIX, 0, "load_config: stat(%s): %s",
filename, strerror(errno)); filename, strerror(errno));
@ -663,29 +685,39 @@ load_config_filev(clicon_handle h,
} }
if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0) if (clicon_xml_parse_file(fd, &xt, "</clicon>") < 0)
goto done; goto done;
if ((xn = xml_child_i(xt, 0)) != NULL){ if (xt == NULL)
goto done;
// if ((xn = xml_child_i(xt, 0)) != NULL){
if ((cbxml = cbuf_new()) == NULL) if ((cbxml = cbuf_new()) == NULL)
goto done; goto done;
x = NULL; x = NULL;
while ((x = xml_child_each(xn, x, -1)) != NULL) while ((x = xml_child_each(xt, x, -1)) != NULL) {
/* Ensure top-level is "config", maybe this is too rough? */
xml_name_set(x, "config");
if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0) if (clicon_xml2cbuf(cbxml, x, 0, 0) < 0)
goto done; goto done;
if (clicon_rpc_xmlput(h, "candidate", }
if (clicon_rpc_edit_config(h, "candidate",
replace?OP_REPLACE:OP_MERGE, replace?OP_REPLACE:OP_MERGE,
"", "",
cbuf_get(cbxml)) < 0) cbuf_get(cbxml)) < 0)
goto done; goto done;
cbuf_free(cbxml); cbuf_free(cbxml);
} // }
ret = 0; ret = 0;
done: done:
unchunk_group(__FUNCTION__);
if (xt) if (xt)
xml_free(xt); xml_free(xt);
if (fd != -1) if (fd != -1)
close(fd); close(fd);
return ret; return ret;
} }
int load_config_filev(clicon_handle h, cvec *vars, cvec *argv)
{
return load_config_file(h, vars, argv);
}
/*! Copy database to local file /*! Copy database to local file
* Utility function used by cligen spec file * Utility function used by cligen spec file
@ -703,13 +735,12 @@ load_config_filev(clicon_handle h,
* @see load_config_file * @see load_config_file
*/ */
int int
save_config_filev(clicon_handle h, save_config_file(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
int retval = -1; int retval = -1;
char **vecp; char *filename = NULL;
char *filename;
cg_var *cv; cg_var *cv;
char *dbstr; char *dbstr;
char *varstr; char *varstr;
@ -736,35 +767,34 @@ save_config_filev(clicon_handle h,
clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr); clicon_err(OE_PLUGIN, 0, "No such var name: %s", varstr);
goto done; goto done;
} }
if ((vecp = clicon_realpath(NULL, cv_string_get(cv), __FUNCTION__)) == NULL){ filename = cv_string_get(cv);
cli_output(stderr, "Failed to resolve filename\n"); if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0)
goto done;
}
filename = vecp[0];
if (xmldb_get(h, dbstr, "/", &xt, NULL, NULL) < 0)
goto done; goto done;
if ((f = fopen(filename, "wb")) == NULL){ if ((f = fopen(filename, "wb")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", filename); clicon_err(OE_CFG, errno, "Creating file %s", filename);
goto done; goto done;
} }
if (xml_print(f, xt) < 0) if (clicon_xml2file(f, xt, 0, 1) < 0)
goto done; goto done;
retval = 0; retval = 0;
/* Fall through */ /* Fall through */
done: done:
unchunk_group(__FUNCTION__);
if (xt) if (xt)
xml_free(xt); xml_free(xt);
if (f != NULL) if (f != NULL)
fclose(f); fclose(f);
return retval; return retval;
} }
int save_config_filev(clicon_handle h, cvec *vars, cvec *argv)
{
return save_config_file(h, vars, argv);
}
/*! Delete all elements in a database /*! Delete all elements in a database
* Utility function used by cligen spec file * Utility function used by cligen spec file
*/ */
int int
delete_allv(clicon_handle h, delete_all(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
@ -782,23 +812,29 @@ delete_allv(clicon_handle h,
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr); clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
goto done; goto done;
} }
if (clicon_rpc_change(h, "candidate", if (clicon_rpc_delete_config(h, dbstr) < 0)
OP_REMOVE,
"/", "") < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
int delete_allv(clicon_handle h, cvec *vars, cvec *argv)
{
return delete_all(h, vars, argv);
}
/*! Discard all changes in candidate and replace with running /*! Discard all changes in candidate and replace with running
*/ */
int int
discard_changesv(clicon_handle h, discard_changes(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
return clicon_rpc_copy(h, "running", "candidate"); return clicon_rpc_discard_changes(h);
}
int discard_changesv(clicon_handle h, cvec *vars, cvec *argv)
{
return discard_changes(h, vars, argv);
} }
/*! Copy from one database to another, eg running->startup /*! Copy from one database to another, eg running->startup
@ -814,15 +850,9 @@ db_copy(clicon_handle h,
db1 = cv_string_get(cvec_i(argv, 0)); db1 = cv_string_get(cvec_i(argv, 0));
db2 = cv_string_get(cvec_i(argv, 1)); db2 = cv_string_get(cvec_i(argv, 1));
return clicon_rpc_copy(h, db1, db2); return clicon_rpc_copy_config(h, db1, db2);
} }
/* These are strings that can be used as 3rd argument to cli_setlog */
static const char *SHOWAS_TXT = "txt";
static const char *SHOWAS_XML = "xml";
static const char *SHOWAS_XML2TXT = "xml2txt";
static const char *SHOWAS_XML2JSON = "xml2json";
/*! This is the callback used by cli_setlog to print log message in CLI /*! This is the callback used by cli_setlog to print log message in CLI
* param[in] s UNIX socket from backend where message should be read * param[in] s UNIX socket from backend where message should be read
* param[in] arg format: txt, xml, xml2txt, xml2json * param[in] arg format: txt, xml, xml2txt, xml2json
@ -831,18 +861,16 @@ static int
cli_notification_cb(int s, cli_notification_cb(int s,
void *arg) void *arg)
{ {
struct clicon_msg *reply; struct clicon_msg *reply = NULL;
enum clicon_msg_type type;
int eof; int eof;
int retval = -1; int retval = -1;
char *eventstr = NULL;
int level;
cxobj *xt = NULL; cxobj *xt = NULL;
cxobj *xn; cxobj *xe;
char *format = (char*)arg; cxobj *x;
enum format_enum format = (enum format_enum)arg;
/* get msg (this is the reason this function is called) */ /* get msg (this is the reason this function is called) */
if (clicon_msg_rcv(s, &reply, &eof, __FUNCTION__) < 0) if (clicon_msg_rcv(s, &reply, &eof) < 0)
goto done; goto done;
if (eof){ if (eof){
clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__); clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__);
@ -851,55 +879,36 @@ cli_notification_cb(int s,
event_unreg_fd(s, cli_notification_cb); event_unreg_fd(s, cli_notification_cb);
goto done; goto done;
} }
if (format == NULL) if (clicon_msg_decode(reply, &xt) < 0)
goto done; goto done;
type = ntohs(reply->op_type); if ((xe = xpath_first(xt, "//event")) != NULL){
switch (type){ x = NULL;
case CLICON_MSG_NOTIFY: while ((x = xml_child_each(xe, x, -1)) != NULL) {
if (clicon_msg_notify_decode(reply, &level, &eventstr, __FUNCTION__) < 0) switch (format){
case FORMAT_XML:
if (clicon_xml2file(stdout, x, 0, 1) < 0)
goto done; goto done;
if (strcmp(format, SHOWAS_TXT) == 0){ break;
fprintf(stdout, "%s", eventstr); case FORMAT_TEXT:
} if (xml2txt(stdout, x, 0) < 0)
else
if (strcmp(format, SHOWAS_XML) == 0){
if (clicon_xml_parse_string(&eventstr, &xt) < 0)
goto done; goto done;
if ((xn = xml_child_i(xt, 0)) != NULL) break;
if (xml_print(stdout, xn) < 0) case FORMAT_JSON:
if (xml2json(stdout, x, 1) < 0)
goto done; goto done;
}
else
if (strcmp(format, SHOWAS_XML2TXT) == 0){
if (clicon_xml_parse_string(&eventstr, &xt) < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL)
if (xml2txt(stdout, xn, 0) < 0)
goto done;
}
else
if (strcmp(format, SHOWAS_XML2JSON) == 0){
if (clicon_xml_parse_string(&eventstr, &xt) < 0)
goto done;
if ((xn = xml_child_i(xt, 0)) != NULL){
if (xml2json(stdout, xn, 0) < 0)
goto done;
}
}
break; break;
default: default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %d",
__FUNCTION__, type);
goto done;
break; break;
} }
}
}
retval = 0; retval = 0;
done: done:
if (xt) if (xt)
xml_free(xt); xml_free(xt);
unchunk_group(__FUNCTION__); /* event allocated by chunk */ if (reply)
free(reply);
return retval; return retval;
} }
/*! Make a notify subscription to backend and un/register callback for return messages. /*! Make a notify subscription to backend and un/register callback for return messages.
@ -916,7 +925,7 @@ cli_notification_cb(int s,
* XXX: format is a memory leak * XXX: format is a memory leak
*/ */
int int
cli_notifyv(clicon_handle h, cli_notify(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
@ -924,7 +933,7 @@ cli_notifyv(clicon_handle h,
int retval = -1; int retval = -1;
int status; int status;
char *formatstr = NULL; char *formatstr = NULL;
enum format_enum format = MSG_NOTIFY_TXT; enum format_enum format = FORMAT_TEXT;
if (cvec_len(argv) != 2 && cvec_len(argv) != 3){ if (cvec_len(argv) != 2 && cvec_len(argv) != 3){
clicon_err(OE_PLUGIN, 0, "%s Requires arguments: <logstream> <status> [<format>]", __FUNCTION__); clicon_err(OE_PLUGIN, 0, "%s Requires arguments: <logstream> <status> [<format>]", __FUNCTION__);
@ -934,8 +943,7 @@ cli_notifyv(clicon_handle h,
status = atoi(cv_string_get(cvec_i(argv, 1))); status = atoi(cv_string_get(cvec_i(argv, 1)));
if (cvec_len(argv) > 2){ if (cvec_len(argv) > 2){
formatstr = cv_string_get(cvec_i(argv, 2)); formatstr = cv_string_get(cvec_i(argv, 2));
if (strcmp(formatstr, "SHOWAS_TXT") != 0) format = format_str2int(formatstr);
format = MSG_NOTIFY_XML;
} }
if (cli_notification_register(h, if (cli_notification_register(h,
stream, stream,
@ -943,198 +951,210 @@ cli_notifyv(clicon_handle h,
"", "",
status, status,
cli_notification_cb, cli_notification_cb,
(void*)formatstr) < 0) (void*)format) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
return retval; return retval;
} }
int cli_notifyv(clicon_handle h, cvec *vars, cvec *argv)
{
return cli_notify(h, vars, argv);
}
/* Here are backward compatible cligen callback functions used when /*! Lock database
* the option: CLICON_CLIGEN_CALLBACK_SINGLE_ARG is set. *
* @param[in] h Clicon handle
* @param[in] cvv Not used
* @param[in] arg A string with <database>
* @code
* lock("comment"), cli_lock("running");
* @endcode
* XXX: format is a memory leak
*/ */
cb_single_arg(cli_set)
cb_single_arg(cli_merge)
cb_single_arg(cli_del)
cb_single_arg(cli_debug_cli)
cb_single_arg(cli_debug_backend)
cb_single_arg(cli_set_mode)
cb_single_arg(cli_start_shell)
cb_single_arg(cli_quit)
//cb_single_arg(cli_commit)
int cli_commit(clicon_handle h, cvec *cvv, cg_var *arg)
{
int retval=-1;
cvec *argv = NULL;
if (arg){
if (cv_type_get(arg) > CGV_EMPTY){
cligen_output(stderr, "%s: Illegal cvtype. This is most probably a single-argument cligen callback being used in a multi-argument setting. This can happen if option CLICON_CLIGEN_CALLBACK_SINGLE_ARG is 0 but you call a single argument callback (eg %s) from a .cli file. Please change to a multi-argument callback\n", __FUNCTION__, __FUNCTION__);
goto done;
}
if ((argv = cvec_from_var(arg)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
}
retval = cli_commitv(h, cvv, argv);
done:
if (argv) cvec_free(argv);
return retval;
}
cb_single_arg(cli_validate)
cb_single_arg(compare_dbs)
cb_single_arg(delete_all)
cb_single_arg(discard_changes)
/* Follows some functions not covered by translation macro */
int int
load_config_file(clicon_handle h, cli_lock(clicon_handle h,
cvec *cvv, cvec *cvv,
cg_var *arg) cvec *argv)
{ {
char *db;
int retval = -1; int retval = -1;
cvec *argv;
cg_var *cv;
char *str;
char **vec;
int nvec;
/* Split string into two parts and build a cvec of it and supply that to if (cvec_len(argv) != 1){
the multi-arg callback */ clicon_err(OE_PLUGIN, 0, "Requires arguments: <db>");
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done; goto done;
} }
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ db = cv_string_get(cvec_i(argv, 0));
clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); if (clicon_rpc_lock(h, db) < 0)
goto done; goto done;
} retval = 0;
if (nvec != 2){
clicon_err(OE_PLUGIN, 0, "Arg syntax is <varname> <replace|merge>");
goto done;
}
if ((argv = cvec_new(nvec)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
cv = cvec_i(argv, 0);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[0]);
cv = cvec_i(argv, 1);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[1]);
retval = load_config_filev(h, cvv, argv);
done: done:
unchunk_group(__FUNCTION__);
return retval; return retval;
} }
/*! Unlock database
*
* @param[in] h Clicon handle
* @param[in] cvv Not used
* @param[in] arg A string with <database>
* @code
* lock("comment"), cli_lock("running");
* @endcode
* XXX: format is a memory leak
*/
int int
save_config_file(clicon_handle h, cli_unlock(clicon_handle h,
cvec *cvv, cvec *cvv,
cg_var *arg) cvec *argv)
{ {
char *db;
int retval = -1; int retval = -1;
cvec *argv;
cg_var *cv;
char *str;
char **vec;
int nvec;
/* Split string into two parts and build a cvec of it and supply that to if (cvec_len(argv) != 1){
the multi-arg callback */ clicon_err(OE_PLUGIN, 0, "Requires arguments: <db>");
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done; goto done;
} }
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ db = cv_string_get(cvec_i(argv, 0));
clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); if (clicon_rpc_unlock(h, db) < 0)
goto done; goto done;
} retval = 0;
if (nvec != 2){
clicon_err(OE_PLUGIN, 0, "Arg syntax is <dbname> <varname>");
goto done;
}
if ((argv = cvec_new(nvec)) == NULL){
clicon_err(OE_UNIX, errno, "cvec_from_var");
goto done;
}
cv = cvec_i(argv, 0);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[0]);
cv = cvec_i(argv, 1);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[1]);
retval = save_config_filev(h, cvv, argv);
done: done:
unchunk_group(__FUNCTION__);
return retval; return retval;
} }
/*! Copy one configuration object to antother
*
* Works for objects that are items ina yang list with a keyname, eg as:
* list sender{
* key name;
* leaf name{...
*
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] argv Vector: <db>, <xpath>, <field>, <fromvar>, <tovar>
* Explanation of argv fields:
* db: Database name, eg candidate|tmp|startup
* xpath: XPATH expression with exactly two %s pointing to field and from name
* field: Name of list key, eg name
* fromvar:Name of variable containing name of object to copy from (given by xpath)
* tovar: Name of variable containing name of object to copy to.
* @code
* cli spec:
* copy snd <n1:string> to <n2:string>, cli_copy_config("candidate", "/sender[%s=%s]", "from", "n1", "n2");
* cli command:
* copy snd from to to
* @endcode
*/
int int
cli_notify(clicon_handle h, cli_copy_config(clicon_handle h,
cvec *cvv, cvec *cvv,
cg_var *arg) cvec *argv)
{ {
int retval = -1; int retval = -1;
cvec *argv; char *db;
cg_var *cv; cxobj *x1 = NULL;
char *str; cxobj *x2 = NULL;
char **vec; cxobj *x;
int nvec; char *xpath;
int i;
int j;
cbuf *cb = NULL;
char *keyname;
char *fromvar;
cg_var *fromcv;
char *fromname = NULL;
char *tovar;
cg_var *tocv;
char *toname;
/* Split string into two parts and build a cvec of it and supply that to if (cvec_len(argv) != 5){
the multi-arg callback */ clicon_err(OE_PLUGIN, 0, "%s: Requires four elements: <db> <xpath> <keyname> <from> <to>", __FUNCTION__);
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done; goto done;
} }
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){ /* First argv argument: Database */
clicon_err(OE_PLUGIN, errno, "clicon_strsplit"); db = cv_string_get(cvec_i(argv, 0));
goto done; /* Second argv argument: xpath */
} xpath = cv_string_get(cvec_i(argv, 1));
if (nvec != 2 && nvec != 3){ /* Third argv argument: name of keyname */
clicon_err(OE_PLUGIN, 0, "Arg syntax is <logstream> <status> [<format>]"); keyname = cv_string_get(cvec_i(argv, 2));
goto done; /* Fourth argv argument: from variable */
} fromvar = cv_string_get(cvec_i(argv, 3));
if ((argv = cvec_new(nvec)) == NULL){ /* Fifth argv argument: to variable */
clicon_err(OE_UNIX, errno, "cvec_from_var"); tovar = cv_string_get(cvec_i(argv, 4));
goto done;
}
cv = cvec_i(argv, 0);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[0]);
cv = cvec_i(argv, 1); /* Get from variable -> cv -> from name */
cv_type_set(cv, CGV_STRING); if ((fromcv = cvec_find_var(cvv, fromvar)) == NULL){
cv_string_set(cv, vec[1]); clicon_err(OE_PLUGIN, 0, "fromvar '%s' not found in cligen var list", fromvar);
if (nvec > 2){ goto done;
cv = cvec_i(argv, 2);
cv_type_set(cv, CGV_STRING);
cv_string_set(cv, vec[2]);
} }
retval = cli_notifyv(h, cvv, argv); /* Get from name from cv */
fromname = cv_string_get(fromcv);
/* Create xpath */
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
/* Sanity check that xpath contains exactly one %s */
j = 0;
for (i=0; i<strlen(xpath); i++)
if (xpath[i] == '%')
j++;
if (j != 2){
clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have two '%%'");
goto done;
}
cprintf(cb, xpath, keyname, fromname);
/* Get from object configuration and store in x1 */
if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0)
goto done;
/* Get to variable -> cv -> to name */
if ((tocv = cvec_find_var(cvv, tovar)) == NULL){
clicon_err(OE_PLUGIN, 0, "tovar '%s' not found in cligen var list", tovar);
goto done;
}
toname = cv_string_get(tocv);
/* Create copy xml tree x2 */
if ((x2 = xml_new("new", NULL)) == NULL)
goto done;
if (xml_copy(x1, x2) < 0)
goto done;
cprintf(cb, "/%s", keyname);
if ((x = xpath_first(x2, cbuf_get(cb))) == NULL){
clicon_err(OE_PLUGIN, 0, "Field %s not found in copy tree", keyname);
goto done;
}
x = xml_find(x, "body");
xml_value_set(x, toname);
/* resuse cb */
cbuf_reset(cb);
/* create xml copy tree and merge it with database configuration */
clicon_xml2cbuf(cb, x2, 0, 0);
if (clicon_rpc_edit_config(h, db, OP_MERGE, NULL, cbuf_get(cb)) < 0)
goto done;
retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (cb)
cbuf_free(cb);
if (x1 != NULL)
xml_free(x1);
if (x2 != NULL)
xml_free(x2);
return retval; return retval;
} }
/*
* cli_debug /*! set debug level on stderr (not syslog).
* set debug level on stderr (not syslog).
* The level is either what is specified in arg as int argument. * The level is either what is specified in arg as int argument.
* _or_ if a 'level' variable is present in vars use that value instead. * _or_ if a 'level' variable is present in vars use that value instead.
* XXX obsolete. Use cli_debug_cliv or cli_debug_backendv instead * XXX obsolete. Use cli_debug_cliv or cli_debug_backendv instead
*/ */
int int
cli_debug(clicon_handle h, cvec *vars, cg_var *arg) cli_debug(clicon_handle h,
cvec *vars,
cg_var *arg)
{ {
cg_var *cv; cg_var *cv;
int level; int level;

View file

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

View file

@ -54,6 +54,7 @@
#include <sys/param.h> #include <sys/param.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <assert.h> #include <assert.h>
#include <libgen.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -69,7 +70,7 @@
#include "cli_handle.h" #include "cli_handle.h"
/* Command line options to be passed to getopt(3) */ /* 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 */ /*! terminate cli application */
static int static int
@ -77,10 +78,10 @@ cli_terminate(clicon_handle h)
{ {
yang_spec *yspec; yang_spec *yspec;
clicon_rpc_close_session(h);
if ((yspec = clicon_dbspec_yang(h)) != NULL) if ((yspec = clicon_dbspec_yang(h)) != NULL)
yspec_free(yspec); yspec_free(yspec);
cli_plugin_finish(h); cli_plugin_finish(h);
exit_candidate_db(h);
cli_handle_exit(h); cli_handle_exit(h);
return 0; return 0;
} }
@ -150,7 +151,8 @@ usage(char *argv0, clicon_handle h)
"\t-p \t\tPrint database yang specification\n" "\t-p \t\tPrint database yang specification\n"
"\t-G \t\tPrint CLI syntax generated from dbspec (if CLICON_CLI_GENMODEL enabled)\n" "\t-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 \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, argv0,
confsock ? confsock : "none", confsock ? confsock : "none",
plgdir ? plgdir : "none" plgdir ? plgdir : "none"
@ -174,7 +176,7 @@ main(int argc, char **argv)
int help = 0; int help = 0;
char *treename; char *treename;
int logdst = CLICON_LOG_STDERR; int logdst = CLICON_LOG_STDERR;
char *restarg; /* what remains after options */ char *restarg = NULL; /* what remains after options */
/* Defaults */ /* Defaults */
@ -288,6 +290,15 @@ main(int argc, char **argv)
case 'L' : /* Debug print dynamic CLI syntax */ case 'L' : /* Debug print dynamic CLI syntax */
logclisyntax++; logclisyntax++;
break; 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: default:
usage(argv[0], h); usage(argv[0], h);
break; break;
@ -359,23 +370,14 @@ main(int argc, char **argv)
goto done; 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) if (logclisyntax)
cli_logsyntax_set(h, logclisyntax); cli_logsyntax_set(h, logclisyntax);
if (debug) if (debug)
clicon_option_dump(h, debug); clicon_option_dump(h, debug);
/* Join rest of argv to a single command */ /* 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 */ /* If several cligen object variables match same preference, select first */
cligen_match_cgvar_same(1); cligen_match_cgvar_same(1);
@ -398,6 +400,8 @@ main(int argc, char **argv)
if (!once) if (!once)
cli_interactive(h); cli_interactive(h);
done: done:
if (restarg)
free(restarg);
unchunk_group(__FUNCTION__); unchunk_group(__FUNCTION__);
// Gets in your face if we log on stderr // Gets in your face if we log on stderr
clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */ clicon_log_init(__PROGRAM__, LOG_INFO, 0); /* Log on syslog no stderr */

View file

@ -372,24 +372,11 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
} }
/* Resolve callback names to function pointers. */ /* 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){ 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)", 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); filename, plgnam, plgnam);
goto done; 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;
}
else
if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, handle) < 0) if (cligen_expandv_str2fn(pt, (expandv_str2fn_t*)clixon_str2fn, handle) < 0)
goto done; goto done;
@ -398,9 +385,8 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
clicon_err(OE_PLUGIN, 0, "No syntax mode specified in %s", filepath); clicon_err(OE_PLUGIN, 0, "No syntax mode specified in %s", filepath);
goto done; goto done;
} }
if ((vec = clicon_strsplit(mode, ":", &nvec, __FUNCTION__)) == NULL) { if ((vec = clicon_strsep(mode, ":", &nvec)) == NULL)
goto done; goto done;
}
for (i = 0; i < nvec; i++) { for (i = 0; i < nvec; i++) {
if (syntax_append(h, cli_syntax(h), vec[i], pt) < 0) { if (syntax_append(h, cli_syntax(h), vec[i], pt) < 0) {
goto done; goto done;
@ -415,6 +401,8 @@ cli_load_syntax(clicon_handle h, const char *filename, const char *clispec_dir)
done: done:
if (vr) if (vr)
cvec_free(vr); cvec_free(vr);
if (vec)
free(vec);
unchunk_group(__FUNCTION__); unchunk_group(__FUNCTION__);
return retval; return retval;
} }
@ -1020,7 +1008,7 @@ cli_ptpush(clicon_handle h, char *mode, char *string, char *op)
return 0; return 0;
pt = &co_cmd->co_pt; pt = &co_cmd->co_pt;
/* vec is the command, eg 'edit policy_option' */ /* 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; goto catch;
co = NULL; co = NULL;
found = 0; found = 0;
@ -1050,7 +1038,8 @@ cli_ptpush(clicon_handle h, char *mode, char *string, char *op)
co_up_set(cc, co_cmd); co_up_set(cc, co_cmd);
} }
catch: catch:
unchunk_group(__FUNCTION__) ; if (vec)
free(vec);
return 0; return 0;
} }

View file

@ -74,9 +74,6 @@
#include "clixon_cli_api.h" #include "clixon_cli_api.h"
#include "cli_common.h" /* internal functions */ #include "cli_common.h" /* internal functions */
static int xml2csv(FILE *f, cxobj *x, cvec *cvv);
//static int xml2csv_raw(FILE *f, cxobj *x);
/*! Completion callback intended for automatically generated data model /*! Completion callback intended for automatically generated data model
* *
* Returns an expand-type list of commands as used by cligen 'expand' * Returns an expand-type list of commands as used by cligen 'expand'
@ -94,7 +91,7 @@ static int xml2csv(FILE *f, cxobj *x, cvec *cvv);
* XXX: helptexts? * XXX: helptexts?
*/ */
int int
expandv_dbvar(void *h, expand_dbvar(void *h,
char *name, char *name,
cvec *cvv, cvec *cvv,
cvec *argv, cvec *argv,
@ -142,12 +139,14 @@ expandv_dbvar(void *h,
*/ */
if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0) if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0)
goto done; goto done;
if (xmldb_get(h, dbstr, xkpath, &xt, &xvec, &xlen) < 0) if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0)
goto done; goto done;
/* One round to detect duplicates /* One round to detect duplicates
* XXX The code below would benefit from some cleanup * XXX The code below would benefit from some cleanup
*/ */
j = 0; j = 0;
if (xpath_vec(xt, xkpath, &xvec, &xlen) < 0)
goto done;
for (i = 0; i < xlen; i++) { for (i = 0; i < xlen; i++) {
char *str; char *str;
x = xvec[i]; x = xvec[i];
@ -187,7 +186,6 @@ expandv_dbvar(void *h,
} }
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__);
if (xvec) if (xvec)
free(xvec); free(xvec);
if (xt) if (xt)
@ -196,13 +194,24 @@ expandv_dbvar(void *h,
free(xkpath); free(xkpath);
return retval; return retval;
} }
int
expandv_dbvar(void *h,
char *name,
cvec *cvv,
cvec *argv,
cvec *commands,
cvec *helptexts)
{
return expand_dbvar(h, name, cvv, argv, commands, helptexts);
}
/*! List files in a directory /*! List files in a directory
*/ */
int int
expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail) expand_dir(char *dir,
int *nr,
char ***commands,
mode_t flags,
int detail)
{ {
DIR *dirp; DIR *dirp;
struct dirent *dp; struct dirent *dp;
@ -312,11 +321,9 @@ expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail)
return retval; return retval;
} }
/*! CLI callback show yang spec. If arg given matches yang argument string */ /*! CLI callback show yang spec. If arg given matches yang argument string */
int int
show_yangv(clicon_handle h, show_yang(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
@ -334,126 +341,69 @@ show_yangv(clicon_handle h,
yang_print(stdout, yn, 0); yang_print(stdout, yn, 0);
return 0; return 0;
} }
int show_yangv(clicon_handle h, cvec *vars, cvec *argv)
#ifdef notused
/*! XML to CSV raw variant
* @see xml2csv
*/
static int
xml2csv_raw(FILE *f, cxobj *x)
{ {
cxobj *xc; return show_yang(h, vars, argv);
cxobj *xb;
int retval = -1;
int i = 0;
xc = NULL;
while ((xc = xml_child_each(x, xc, CX_ELMNT)) != NULL) {
if (xml_child_nr(xc)){
xb = xml_child_i(xc, 0);
if (xml_type(xb) == CX_BODY){
if (i++)
fprintf(f, ";");
fprintf(f, "%s", xml_value(xb));
}
}
}
fprintf(f, "\n");
retval = 0;
return retval;
}
#endif
/*! Translate XML -> CSV commands
* Can only be made in a 'flat tree', ie on the form:
* <X><A>B</A></X> -->
* Type, A
* X, B
* @param[in] f Output file
* @param[in] x XML tree
* @param[in] cvv A vector of field names present in XML
* This means that only fields in x that are listed in cvv will be printed.
*/
static int
xml2csv(FILE *f, cxobj *x, cvec *cvv)
{
cxobj *xe, *xb;
int retval = -1;
cg_var *vs;
fprintf(f, "%s", xml_name(x));
xe = NULL;
vs = NULL;
while ((vs = cvec_each(cvv, vs))) {
if ((xe = xml_find(x, cv_name_get(vs))) == NULL){
fprintf(f, ";");
continue;
}
if (xml_child_nr(xe)){
xb = xml_child_i(xe, 0);
fprintf(f, ";%s", xml_value(xb));
}
}
fprintf(f, "\n");
retval = 0;
return retval;
} }
/*! Generic function for showing configurations. /*! Generic show configuration CLIGEN callback
* Utility function used by cligen spec file * Utility function used by cligen spec file
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line * @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>] * @param[in] argv String vector: <dbname> <format> <xpath> [<varname>]
* @param[out] xt Configuration as xml tree. * Format of argv:
* Format of arg: * <dbname> "running"|"candidate"|"startup"
* <dbname> "running", "candidate", "startup" * <dbname> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression * <xpath> xpath expression, that may contain one %, eg "/sender[name=%s]"
* <varname> optional name of variable in cvv. If set, xpath must have a '%s' * <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code * @code
* show config id <n:string>, show_conf_as("running interfaces/interface[name=%s] n"); * show config id <n:string>, cli_show_config("running","xml","iface[name=%s]","n");
* @endcode * @endcode
*/ */
static int int
show_confv_as(clicon_handle h, cli_show_config(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv, cvec *argv)
cxobj **xt) /* top xml */
{ {
int retval = -1; int retval = -1;
char *db; char *db;
char *formatstr;
char *xpath; char *xpath;
enum format_enum format;
cbuf *cbxpath = NULL;
char *attr = NULL; char *attr = NULL;
cbuf *cbx = NULL;
int i; int i;
int j; int j;
cg_var *cvattr; cg_var *cvattr;
char *val = NULL; char *val = NULL;
cxobj *xt = NULL;
cxobj *xc;
enum genmodel_type gt;
if (cvec_len(argv) != 2 && cvec_len(argv) != 3){ if (cvec_len(argv) != 3 && cvec_len(argv) != 4){
if (cvec_len(argv)==1) clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<attr>]", cvec_len(argv));
clicon_err(OE_PLUGIN, 0, "Got single argument:\"%s\". Expected \"<dbname>,<xpath>[,<attr>]\"", cv_string_get(cvec_i(argv,0)));
else
clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<xpath>[,<attr>]", cvec_len(argv));
goto done; goto done;
} }
/* Dont get attr here, take it from arg instead */ /* First argv argument: Database */
db = cv_string_get(cvec_i(argv, 0)); db = cv_string_get(cvec_i(argv, 0));
if (strcmp(db, "running") != 0 && /* Second argv argument: Format */
strcmp(db, "candidate") != 0 && formatstr = cv_string_get(cvec_i(argv, 1));
strcmp(db, "startup") != 0) { if ((format = format_str2int(formatstr)) < 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", db); clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
goto done; goto done;
} }
xpath = cv_string_get(cvec_i(argv, 1)); /* Third argv argument: xpath */
if ((cbx = cbuf_new()) == NULL){ xpath = cv_string_get(cvec_i(argv, 2));
/* Create XPATH variable string */
if ((cbxpath = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new"); clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done; goto done;
} }
if (cvec_len(argv) == 3){ /* Fourth argument is stdarg to xpath format string */
attr = cv_string_get(cvec_i(argv, 2)); if (cvec_len(argv) == 4){
attr = cv_string_get(cvec_i(argv, 3));
j = 0; j = 0;
for (i=0; i<strlen(xpath); i++) for (i=0; i<strlen(xpath); i++)
if (xpath[i] == '%') if (xpath[i] == '%')
@ -470,233 +420,55 @@ show_confv_as(clicon_handle h,
clicon_err(OE_PLUGIN, errno, "cv2str_dup"); clicon_err(OE_PLUGIN, errno, "cv2str_dup");
goto done; goto done;
} }
cprintf(cbx, xpath, val); cprintf(cbxpath, xpath, val);
} }
else else
cprintf(cbx, "%s", xpath); cprintf(cbxpath, "%s", xpath);
if (xmldb_get(h, db, cbuf_get(cbx), xt, NULL, NULL) < 0) /* Get configuration from database */
if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0)
goto done; goto done;
retval = 0; /* Print configuration according to format */
done: switch (format){
if (val) case FORMAT_XML:
free(val);
if (cbx)
cbuf_free(cbx);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Show a configuration database on stdout using XML format
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @param[in] netconf If set print as netconf edit-config, otherwise just xml
* @see show_conf_as the main function
*/
static int
show_confv_as_xml1(clicon_handle h,
cvec *cvv,
cvec *argv,
int netconf)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
if (show_confv_as(h, cvv, argv, &xt) < 0)
goto done;
if (netconf) /* netconf prefix */
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL) while ((xc = xml_child_each(xt, xc, -1)) != NULL)
clicon_xml2file(stdout, xc, netconf?2:0, 1); clicon_xml2file(stdout, xc, 0, 1);
if (netconf) /* netconf postfix */ break;
fprintf(stdout, "</config></edit-config></rpc>]]>]]>\n"); case FORMAT_JSON:
retval = 0;
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Show configuration as prettyprinted xml
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_confv_as_xml(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return show_confv_as_xml1(h, cvv, argv, 0);
}
/*! Show configuration as prettyprinted xml with netconf hdr/tail
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_confv_as_netconf(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return show_confv_as_xml1(h, cvv, argv, 1);
}
/*! Show configuration as JSON
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_confv_as_json(clicon_handle h,
cvec *cvv,
cvec *argv)
{
cxobj *xt = NULL;
int retval = -1;
if (show_confv_as(h, cvv, argv, &xt) < 0)
goto done;
xml2json(stdout, xt, 1); xml2json(stdout, xt, 1);
retval = 0; break;
done: case FORMAT_TEXT:
if (xt)
xml_free(xt);
return retval;
}
/*! Show configuration as text
* Utility function used by cligen spec file
*/
static int
show_confv_as_text1(clicon_handle h,
cvec *cvv,
cvec *argv)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
if (show_confv_as(h, cvv, argv, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL) while ((xc = xml_child_each(xt, xc, -1)) != NULL)
xml2txt(stdout, xc, 0); /* tree-formed text */ xml2txt(stdout, xc, 0); /* tree-formed text */
retval = 0; break;
done: case FORMAT_CLI:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
/* Show configuration as commands, ie not tree format but as one-line commands
*/
static int
show_confv_as_command(clicon_handle h,
cvec *cvv,
cvec *argv,
char *prepend)
{
cxobj *xt = NULL;
cxobj *xc;
enum genmodel_type gt;
int retval = -1;
if ((xt = xml_new("tmp", NULL)) == NULL)
goto done;
if (show_confv_as(h, cvv, argv, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL){ while ((xc = xml_child_each(xt, xc, -1)) != NULL){
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done; goto done;
xml2cli(stdout, xc, prepend, gt, __FUNCTION__); /* cli syntax */ xml2cli(stdout, xc, NULL, gt); /* cli syntax */
} }
retval = 0; break;
done: case FORMAT_NETCONF:
if (xt) fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
int
show_confv_as_text(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return show_confv_as_text1(h, cvv, argv);
}
int
show_confv_as_cli(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return show_confv_as_command(h, cvv, argv, NULL); /* XXX: how to set prepend? */
}
static int
show_confv_as_csv1(clicon_handle h,
cvec *cvv0,
cvec *argv)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
cvec *cvv=NULL;
char *str;
if (show_confv_as(h, cvv0, argv, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL){ while ((xc = xml_child_each(xt, xc, -1)) != NULL)
if ((str = chunk_sprintf(__FUNCTION__, "%s[]", xml_name(xc))) == NULL) clicon_xml2file(stdout, xc, 2, 1);
goto done; fprintf(stdout, "</config></edit-config></rpc>]]>]]>\n");
#ifdef NOTYET /* yang-spec? */ break;
if (ds==NULL && (ds = key2spec_key(dbspec, str)) != NULL){
cg_var *vs;
fprintf(stdout, "Type");
cvv = db_spec2cvec(ds);
vs = NULL;
while ((vs = cvec_each(cvv, vs)))
fprintf(stdout, ";%s", cv_name_get(vs));
fprintf(stdout, "\n");
} /* Now values just need to follow,... */
#endif /* yang-spec? */
if (cvv== NULL)
goto done;
xml2csv(stdout, xc, cvv); /* csv syntax */
} }
retval = 0; retval = 0;
done: done:
if (xt) if (xt)
xml_free(xt); xml_free(xt);
unchunk_group(__FUNCTION__); if (val)
free(val);
if (cbxpath)
cbuf_free(cbxpath);
return retval; return retval;
} }
int
show_confv_as_csv(clicon_handle h,
cvec *cvv,
cvec *argv)
{
return show_confv_as_csv1(h, cvv, argv);
}
/*! Show configuration as text given an xpath /*! Show configuration as text given an xpath
* Utility function used by cligen spec file * Utility function used by cligen spec file
* @param[in] h CLICON handle * @param[in] h CLICON handle
@ -705,7 +477,7 @@ show_confv_as_csv(clicon_handle h,
* @note Hardcoded that a variable in cvv is named "xpath" * @note Hardcoded that a variable in cvv is named "xpath"
*/ */
int int
show_confv_xpath(clicon_handle h, show_conf_xpath(clicon_handle h,
cvec *cvv, cvec *cvv,
cvec *argv) cvec *argv)
{ {
@ -732,7 +504,9 @@ show_confv_xpath(clicon_handle h,
} }
cv = cvec_find_var(cvv, "xpath"); cv = cvec_find_var(cvv, "xpath");
xpath = cv_string_get(cv); xpath = cv_string_get(cv);
if (xmldb_get(h, str, xpath, &xt, &xv, &xlen) < 0) if (clicon_rpc_get_config(h, str, xpath, &xt) < 0)
goto done;
if (xpath_vec(xt, xpath, &xv, &xlen) < 0)
goto done; goto done;
for (i=0; i<xlen; i++) for (i=0; i<xlen; i++)
xml_print(stdout, xv[i]); xml_print(stdout, xv[i]);
@ -743,468 +517,9 @@ done:
free(xv); free(xv);
if (xt) if (xt)
xml_free(xt); xml_free(xt);
unchunk_group(__FUNCTION__);
return retval; return retval;
} }
int show_confv_xpath(clicon_handle h, cvec *vars, cvec *argv)
/*=================================================================
* Here are backward compatible cligen callback functions used when
* the option: CLICON_CLIGEN_CALLBACK_SINGLE_ARG is set.
*/
cb_single_arg(show_yang)
/*! This is obsolete version of expandv_dbvar
* If CLICON_CLIGEN_EXPAND_SINGLE_ARG is set
*/
int
expand_dbvar(void *h,
char *name,
cvec *cvv,
cg_var *arg,
int *nr,
char ***commands,
char ***helptexts)
{ {
int nvec; return show_conf_xpath(h, vars, argv);
char **vec = NULL;
int retval = -1;
char *xkfmt;
char *str;
char *dbstr;
cxobj *xt = NULL;
char *xkpath = NULL;
cxobj **xvec = NULL;
size_t xlen = 0;
cxobj *x;
char *bodystr;
int i;
int j;
int k;
int i0;
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
/* In the example, str = "candidate /x/m1/%s/b" */
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
dbstr = vec[0];
if (strcmp(dbstr, "running") != 0 &&
strcmp(dbstr, "candidate") != 0 &&
strcmp(dbstr, "startup") != 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", dbstr);
goto done;
}
xkfmt = vec[1];
/* xkfmt = /interface=%s/address=%s
--> /interface=eth0/address=1.2.3.4
*/
if (xmlkeyfmt2xpath(xkfmt, cvv, &xkpath) < 0)
goto done;
if (xmldb_get(h, dbstr, xkpath, &xt, &xvec, &xlen) < 0)
goto done;
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
*/
j = 0;
for (i = 0; i < xlen; i++) {
char *str;
x = xvec[i];
if (xml_type(x) == CX_BODY)
bodystr = xml_value(x);
else
bodystr = xml_body(x);
if (bodystr == NULL){
clicon_err(OE_CFG, 0, "No xml body");
goto done;
}
/* detect duplicates */
for (k=0; k<j; k++){
if (xml_type(xvec[k]) == CX_BODY)
str = xml_value(xvec[k]);
else
str = xml_body(xvec[k]);
if (strcmp(str, bodystr)==0)
break;
}
if (k==j) /* not duplicate */
xvec[j++] = x;
}
xlen = j;
i0 = *nr;
*nr += xlen;
if ((*commands = realloc(*commands, sizeof(char *) * (*nr))) == NULL) {
clicon_err(OE_UNIX, errno, "realloc: %s", strerror (errno));
goto done;
}
for (i = 0; i < xlen; i++) {
x = xvec[i];
if (xml_type(x) == CX_BODY)
bodystr = xml_value(x);
else
bodystr = xml_body(x);
if (bodystr == NULL){
clicon_err(OE_CFG, 0, "No xml body");
goto done;
}
(*commands)[i0+i] = strdup(bodystr);
}
retval = 0;
done:
unchunk_group(__FUNCTION__);
if (xvec)
free(xvec);
if (xt)
xml_free(xt);
if (xkpath)
free(xkpath);
return retval;
}
/*! Generic function for showing configurations.
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @param[out] xt Configuration as xml tree.
* Format of arg:
* <dbname> "running", "candidate", "startup"
* <xpath> xpath expression
* <varname> optional name of variable in cvv. If set, xpath must have a '%s'
* @code
* show config id <n:string>, show_conf_as("running interfaces/interface[name=%s] n");
* @endcode
*/
static int
show_conf_as(clicon_handle h,
cvec *cvv,
cg_var *arg,
cxobj **xt) /* top xml */
{
int retval = -1;
char *db;
char **vec = NULL;
int nvec;
char *str;
char *xpath;
char *attr = NULL;
cbuf *cbx = NULL;
int i;
int j;
cg_var *cvattr;
char *val = NULL;
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
if ((vec = clicon_strsplit(str, " ", &nvec, __FUNCTION__)) == NULL){
clicon_err(OE_PLUGIN, errno, "clicon_strsplit");
goto done;
}
if (nvec != 2 && nvec != 3){
clicon_err(OE_PLUGIN, 0, "format error \"%s\" - expected <dbname> <xpath> [<attr>] got %d arg", str, nvec);
goto done;
}
/* Dont get attr here, take it from arg instead */
db = vec[0];
if (strcmp(db, "running") != 0 &&
strcmp(db, "candidate") != 0 &&
strcmp(db, "startup") != 0) {
clicon_err(OE_PLUGIN, 0, "No such db name: %s", db);
goto done;
}
xpath = vec[1];
if ((cbx = cbuf_new()) == NULL){
clicon_err(OE_PLUGIN, errno, "cbuf_new");
goto done;
}
if (nvec == 3){
attr = vec[2];
j = 0;
for (i=0; i<strlen(xpath); i++)
if (xpath[i] == '%')
j++;
if (j != 1){
clicon_err(OE_PLUGIN, 0, "xpath '%s' does not have a single '%%'");
goto done;
}
if ((cvattr = cvec_find_var(cvv, attr)) == NULL){
clicon_err(OE_PLUGIN, 0, "attr '%s' not found in cligen var list", attr);
goto done;
}
if ((val = cv2str_dup(cvattr)) == NULL){
clicon_err(OE_PLUGIN, errno, "cv2str_dup");
goto done;
}
cprintf(cbx, xpath, val);
}
else
cprintf(cbx, "%s", xpath);
if (xmldb_get(h, db, cbuf_get(cbx), xt, NULL, NULL) < 0)
goto done;
retval = 0;
done:
if (val)
free(val);
if (cbx)
cbuf_free(cbx);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Show a configuration database on stdout using XML format
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @param[in] netconf If set print as netconf edit-config, otherwise just xml
* @see show_conf_as the main function
*/
static int
show_conf_as_xml1(clicon_handle h,
cvec *cvv,
cg_var *arg,
int netconf)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
if (netconf) /* netconf prefix */
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL)
clicon_xml2file(stdout, xc, netconf?2:0, 1);
if (netconf) /* netconf postfix */
fprintf(stdout, "</config></edit-config></rpc>]]>]]>\n");
retval = 0;
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Show configuration as prettyprinted xml
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_conf_as_xml(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
return show_conf_as_xml1(h, cvv, arg, 0);
}
/*! Show configuration as prettyprinted xml with netconf hdr/tail
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_conf_as_netconf(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
return show_conf_as_xml1(h, cvv, arg, 1);
}
/*! Show configuration as JSON
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath> [<varname>]
* @see show_conf_as the main function
*/
int
show_conf_as_json(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
cxobj *xt = NULL;
int retval = -1;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
xml2json(stdout, xt, 1);
retval = 0;
done:
if (xt)
xml_free(xt);
return retval;
}
/*! Show configuration as text
* Utility function used by cligen spec file
*/
static int
show_conf_as_text1(clicon_handle h, cvec *cvv, cg_var *arg)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL)
xml2txt(stdout, xc, 0); /* tree-formed text */
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
/* Show configuration as commands, ie not tree format but as one-line commands
*/
static int
show_conf_as_command(clicon_handle h, cvec *cvv, cg_var *arg, char *prepend)
{
cxobj *xt = NULL;
cxobj *xc;
enum genmodel_type gt;
int retval = -1;
if ((xt = xml_new("tmp", NULL)) == NULL)
goto done;
if (show_conf_as(h, cvv, arg, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL){
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done;
xml2cli(stdout, xc, prepend, gt, __FUNCTION__); /* cli syntax */
}
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
int
show_conf_as_text(clicon_handle h, cvec *cvv, cg_var *arg)
{
return show_conf_as_text1(h, cvv, arg);
}
int
show_conf_as_cli(clicon_handle h, cvec *cvv, cg_var *arg)
{
return show_conf_as_command(h, cvv, arg, NULL); /* XXX: how to set prepend? */
}
static int
show_conf_as_csv1(clicon_handle h, cvec *cvv0, cg_var *arg)
{
cxobj *xt = NULL;
cxobj *xc;
int retval = -1;
cvec *cvv=NULL;
char *str;
if (show_conf_as(h, cvv0, arg, &xt) < 0)
goto done;
xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, -1)) != NULL){
if ((str = chunk_sprintf(__FUNCTION__, "%s[]", xml_name(xc))) == NULL)
goto done;
#ifdef NOTYET /* yang-spec? */
if (ds==NULL && (ds = key2spec_key(dbspec, str)) != NULL){
cg_var *vs;
fprintf(stdout, "Type");
cvv = db_spec2cvec(ds);
vs = NULL;
while ((vs = cvec_each(cvv, vs)))
fprintf(stdout, ";%s", cv_name_get(vs));
fprintf(stdout, "\n");
} /* Now values just need to follow,... */
#endif /* yang-spec? */
if (cvv== NULL)
goto done;
xml2csv(stdout, xc, cvv); /* csv syntax */
}
retval = 0;
done:
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
}
int
show_conf_as_csv(clicon_handle h, cvec *cvv, cg_var *arg)
{
return show_conf_as_csv1(h, cvv, arg);
}
/*! Show configuration as text given an xpath
* Utility function used by cligen spec file
* @param[in] h CLICON handle
* @param[in] cvv Vector of variables from CLIgen command-line
* @param[in] arg A string: <dbname> <xpath>
* @note Hardcoded that a variable in cvv is named "xpath"
*/
int
show_conf_xpath(clicon_handle h,
cvec *cvv,
cg_var *arg)
{
int retval = -1;
char *str;
char *xpath;
cg_var *cv;
cxobj *xt = NULL;
cxobj **xv = NULL;
size_t xlen;
int i;
if (arg == NULL || (str = cv_string_get(arg)) == NULL){
clicon_err(OE_PLUGIN, 0, "%s: requires string argument", __FUNCTION__);
goto done;
}
/* Dont get attr here, take it from arg instead */
if (strcmp(str, "running") != 0 &&
strcmp(str, "candidate") != 0 &&
strcmp(str, "startup") != 0){
clicon_err(OE_PLUGIN, 0, "No such db name: %s", str);
goto done;
}
cv = cvec_find_var(cvv, "xpath");
xpath = cv_string_get(cv);
if (xmldb_get(h, str, xpath, &xt, &xv, &xlen) < 0)
goto done;
for (i=0; i<xlen; i++)
xml_print(stdout, xv[i]);
retval = 0;
done:
if (xv)
free(xv);
if (xt)
xml_free(xt);
unchunk_group(__FUNCTION__);
return retval;
} }

View file

@ -68,70 +68,82 @@ int cli_handle_exit(clicon_handle h);
cligen_handle cli_cligen(clicon_handle h); cligen_handle cli_cligen(clicon_handle h);
/* cli_common.c */ /* 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, int cli_notification_register(clicon_handle h, char *stream, enum format_enum format,
char *filter, int status, char *filter, int status,
int (*fn)(int, void*), void *arg); int (*fn)(int, void*), void *arg);
#define cli_output cligen_output #define cli_output cligen_output
/* cli_common.c: CLIgen new vector callbacks */ /* 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_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_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_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_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_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_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_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_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_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 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 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 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 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 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 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); int cli_notifyv(clicon_handle h, cvec *cvv, cvec *argv);
/* cli_common.c: CLIgen old single arg callbacks */ int db_copy(clicon_handle h, cvec *cvv, cvec *argv);
int cli_set(clicon_handle h, cvec *vars, cg_var *arg);
int cli_merge(clicon_handle h, cvec *vars, cg_var *arg); int cli_lock(clicon_handle h, cvec *cvv, cvec *argv);
int cli_del(clicon_handle h, cvec *vars, cg_var *arg); int cli_unlock(clicon_handle h, cvec *cvv, cvec *argv);
int cli_debug_cli(clicon_handle h, cvec *vars, cg_var *arg); int cli_copy_config(clicon_handle h, cvec *cvv, cvec *argv);
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);
/* In cli_show.c */ /* In cli_show.c */
int expand_dir(char *dir, int *nr, char ***commands, mode_t flags, int detail); 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, int expandv_dbvar(void *h, char *name, cvec *cvv, cvec *argv,
cvec *commands, cvec *helptexts); cvec *commands, cvec *helptexts);
/* cli_show.c: CLIgen new vector arg callbacks */ /* cli_show.c: CLIgen new vector arg callbacks */
int show_confv_as_xml(clicon_handle h, cvec *vars, cvec *argv); int show_yang(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_yangv(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); int show_confv_xpath(clicon_handle h, cvec *cvv, cvec *argv);
/* cli_show.c: CLIgen old single arg callbacks */ int cli_show_config(clicon_handle h, cvec *cvv, cvec *argv);
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);
#endif /* _CLIXON_CLI_API_H_ */ #endif /* _CLIXON_CLI_API_H_ */

View file

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

View file

@ -44,10 +44,8 @@
*/ */
typedef int (*netconf_cb_t)( typedef int (*netconf_cb_t)(
clicon_handle h, clicon_handle h,
cxobj *xorig, /* Original request. */ cxobj *xn, /* Request: <rpc><xn></rpc> */
cxobj *xn, /* Sub-tree (under xorig) at child: <rpc><xn></rpc> */ cxobj **xret, /* Return xml tree, eg <rpc-reply>... */
cbuf *cb, /* Output xml stream. For reply */
cbuf *cb_err, /* Error xml stream. For error reply */
void *arg /* Argument given at netconf_register_callback() */ 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_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, int netconf_register_callback(clicon_handle h,
netconf_cb_t cb, /* Callback called */ netconf_cb_t cb, /* Callback called */
void *arg, /* Arg to send to callback */ void *arg, /* Arg to send to callback */
char *tag); /* Xml tag when callback is made */ 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, int netconf_xpath(cxobj *xsearch,
cxobj *xfilter, cxobj *xfilter,

View file

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

View file

@ -68,22 +68,8 @@
/* /*
* Exported variables * Exported variables
*/ */
enum transport_type transport = NETCONF_SSH; enum transport_type transport = NETCONF_SSH; /* XXX Remove SOAP support */
int cc_closed = 0; int cc_closed = 0; /* XXX Please remove (or at least hide in handle) this global variable */
static int cc_ok = 0;
void
netconf_ok_set(int ok)
{
cc_ok = ok;
}
int
netconf_ok_get(void)
{
return cc_ok;
}
int int
add_preamble(cbuf *xf) add_preamble(cbuf *xf)
@ -161,38 +147,13 @@ add_error_postamble(cbuf *xf)
return 0; 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 /*! Get "target" attribute, return actual database given candidate or running
* Caller must do error handling * Caller must do error handling
* @retval dbname Actual database file name * @retval dbname Actual database file name
*/ */
char * char *
netconf_get_target(clicon_handle h, netconf_get_target(cxobj *xn,
cxobj *xn,
char *path) char *path)
{ {
cxobj *x; cxobj *x;
@ -218,7 +179,9 @@ netconf_get_target(clicon_handle h,
* @param[in] msg Only for debug * @param[in] msg Only for debug
*/ */
int int
netconf_output(int s, cbuf *xf, char *msg) netconf_output(int s,
cbuf *xf,
char *msg)
{ {
char *buf = cbuf_get(xf); char *buf = cbuf_get(xf);
int len = cbuf_len(xf); int len = cbuf_len(xf);

View file

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

View file

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

View file

@ -66,6 +66,15 @@
#include "netconf_lib.h" #include "netconf_lib.h"
#include "netconf_plugin.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 /*! Unload a plugin
*/ */
static int static int
@ -245,10 +254,9 @@ catch:
/*! See if there is any callback registered for this tag /*! See if there is any callback registered for this tag
* *
* @param xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>. * @param[in] h clicon handle
* @param xf Output xml stream. For reply * @param[in] xn Sub-tree (under xorig) at child of rpc: <rpc><xn></rpc>.
* @param xf_err Error xml stream. For error reply * @param[out] xret Return XML, error or OK
* @param xorig Original request.
* *
* @retval -1 Error * @retval -1 Error
* @retval 0 OK, not found handler. * @retval 0 OK, not found handler.
@ -257,30 +265,23 @@ catch:
int int
netconf_plugin_callbacks(clicon_handle h, netconf_plugin_callbacks(clicon_handle h,
cxobj *xn, cxobj *xn,
cbuf *xf, cxobj **xret)
cbuf *xf_err,
cxobj *xorig)
{ {
netconf_reg_t *nr; netconf_reg_t *nreg;
int retval; int retval;
if (deps == NULL) if (deps == NULL)
return 0; return 0;
nr = deps; nreg = deps;
do { do {
if (strcmp(nr->nr_tag, xml_name(xn)) == 0){ if (strcmp(nreg->nr_tag, xml_name(xn)) == 0){
if ((retval = nr->nr_callback(h, if ((retval = nreg->nr_callback(h, xn, xret, nreg->nr_arg)) < 0)
xorig,
xn,
xf,
xf_err,
nr->nr_arg)) < 0)
return -1; return -1;
else else
return 1; /* handled */ return 1; /* handled */
} }
nr = NEXTQ(netconf_reg_t *, nr); nreg = NEXTQ(netconf_reg_t *, nreg);
} while (nr != deps); } while (nreg != deps);
return 0; return 0;
} }

View file

@ -41,14 +41,6 @@
* Types * 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 * 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_unload(clicon_handle h);
int netconf_plugin_callbacks(clicon_handle h, cxobj *xn, cxobj **xret);
int netconf_plugin_callbacks(clicon_handle h,
// dbspec_key *dbspec,
cxobj *xn,
cbuf *xf,
cbuf *xf_err,
cxobj *xt);
#endif /* _NETCONF_PLUGIN_H_ */ #endif /* _NETCONF_PLUGIN_H_ */

File diff suppressed because it is too large Load diff

View file

@ -42,20 +42,7 @@
*/ */
int int
netconf_rpc_dispatch(clicon_handle h, netconf_rpc_dispatch(clicon_handle h,
cxobj *xorig,
cxobj *xn, cxobj *xn,
cbuf *xf, cxobj **xret);
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);
#endif /* _NETCONF_RPC_H_ */ #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@ 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_lib.c
SRC += restconf_methods.c
OBJS = $(SRC:.c=.o) 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 # Define nginx config file/etc/nginx/sites-available/default
server { 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: Start the restconf programs with debug flag:
sudo su -c "/www-data/clixon_restconf -D" -s /bin/sh www-data 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 <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>

View file

@ -1,9 +1,36 @@
/* /*
* *
* $COPYRIGHTSTATEMENT$ ***** BEGIN LICENSE BLOCK *****
*
* $LICENSE$ Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
* This is backend headend sender code, ie communication with a pmagent
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_ #ifndef _RESTCONF_LIB_H_
@ -12,13 +39,6 @@
/* /*
* Constants * 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 * Prototypes

View file

@ -59,6 +59,7 @@
#include <sys/time.h> #include <sys/time.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <curl/curl.h> #include <curl/curl.h>
#include <libgen.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -66,302 +67,17 @@
/* clicon */ /* clicon */
#include <clixon/clixon.h> #include <clixon/clixon.h>
/* restconf */
#include "restconf_lib.h" #include "restconf_lib.h"
#include "restconf_methods.h"
/* Command line options to be passed to getopt(3) */ /* 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" /* Should be discovered via "/.well-known/host-meta"
resource ([RFC6415]) */ resource ([RFC6415]) */
#define RESTCONF_API_ROOT "/restconf/" #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 /*! Generic REST method, GET, PUT, DELETE
* @param[in] h CLIXON handle * @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle * @param[in] r Fastcgi request handle
@ -385,14 +101,19 @@ api_data(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
request_method = FCGX_GetParam("REQUEST_METHOD", r->envp); request_method = FCGX_GetParam("REQUEST_METHOD", r->envp);
clicon_debug(1, "%s method:%s", __FUNCTION__, request_method);
if (strcmp(request_method, "OPTIONS")==0) 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) else if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, r, pcvec, pi, qvec); 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) 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) else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, api_path, pi); retval = api_data_delete(h, r, api_path, pi);
else else
@ -411,7 +132,7 @@ request_process(clicon_handle h,
char *path; char *path;
char *query; char *query;
char *method; char *method;
char **pvec; char **pvec = NULL;
int pn; int pn;
cvec *qvec = NULL; cvec *qvec = NULL;
cvec *dvec = NULL; cvec *dvec = NULL;
@ -423,7 +144,7 @@ request_process(clicon_handle h,
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("DOCUMENT_URI", r->envp); path = FCGX_GetParam("DOCUMENT_URI", r->envp);
query = FCGX_GetParam("QUERY_STRING", 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; goto done;
if (str2cvec(query, '&', '=', &qvec) < 0) if (str2cvec(query, '&', '=', &qvec) < 0)
@ -437,7 +158,12 @@ request_process(clicon_handle h,
clicon_debug(1, "DATA=%s", data); clicon_debug(1, "DATA=%s", data);
if (str2cvec(data, '&', '=', &dvec) < 0) if (str2cvec(data, '&', '=', &dvec) < 0)
goto done; goto done;
method = pvec[2];
if ((method = pvec[2]) == NULL){
retval = notfound(r);
goto done;
}
retval = 0; retval = 0;
test(r, 1); test(r, 1);
/* If present, check credentials */ /* If present, check credentials */
@ -457,6 +183,8 @@ request_process(clicon_handle h,
retval = notfound(r); retval = notfound(r);
done: done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (pvec)
free(pvec);
if (dvec) if (dvec)
cvec_free(dvec); cvec_free(dvec);
if (qvec) if (qvec)
@ -465,10 +193,43 @@ request_process(clicon_handle h,
cvec_free(pcvec); cvec_free(pcvec);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
unchunk_group(__FUNCTION__);
return retval; 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 /*! Usage help routine
* @param[in] argv0 command line * @param[in] argv0 command line
* @param[in] h Clicon handle * @param[in] h Clicon handle
@ -485,7 +246,8 @@ usage(clicon_handle h,
"\t-h \t\tHelp\n" "\t-h \t\tHelp\n"
"\t-D \t\tDebug. Log to syslog\n" "\t-D \t\tDebug. Log to syslog\n"
"\t-f <file>\tConfiguration file (mandatory)\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, argv0,
restconfdir restconfdir
); );
@ -506,13 +268,14 @@ main(int argc,
char *sockpath; char *sockpath;
char *path; char *path;
clicon_handle h; clicon_handle h;
char *yangspec=NULL;
/* In the startup, logs to stderr & debug flag set later */ /* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG); clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_SYSLOG);
/* Create handle */ /* Create handle */
if ((h = clicon_handle_init()) == NULL) if ((h = clicon_handle_init()) == NULL)
goto done; goto done;
_CLICON_HANDLE = h; /* for termination handling */
while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1) while ((c = getopt(argc, argv, RESTCONF_OPTS)) != -1)
switch (c) { switch (c) {
case 'h': case 'h':
@ -531,6 +294,9 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg); clicon_option_str_set(h, "CLICON_RESTCONF_DIR", optarg);
break; break;
case 'y' : /* yang module */
yangspec = optarg;
break;
default: default:
usage(h, argv[0]); usage(h, argv[0]);
break; break;
@ -540,11 +306,29 @@ main(int argc,
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG); clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_debug_init(debug, NULL); 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 */ /* Find and read configfile */
if (clicon_options_main(h) < 0) if (clicon_options_main(h) < 0)
goto done; 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 */ /* Initialize plugins group */
if (restconf_plugin_load(h) < 0) if (restconf_plugin_load(h) < 0)
return -1; return -1;
@ -592,5 +376,6 @@ main(int argc,
retval = 0; retval = 0;
done: done:
restconf_plugin_unload(h); restconf_plugin_unload(h);
restconf_terminate(h);
return retval; 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

@ -32,25 +32,32 @@
***** END LICENSE BLOCK ***** ***** END LICENSE BLOCK *****
*/ */
#ifndef _CLIXON_XML_DB_RPC_H_
#define _CLIXON_XML_DB_RPC_H_
#ifndef _RESTCONF_METHODS_H_
#define _RESTCONF_METHODS_H_
/*
* Constants
*/
/* /*
* Prototypes * Prototypes
*/ */
int xmldb_get_rpc(clicon_handle h, char *db, int api_data_options(clicon_handle h, FCGX_Request *r);
char *xpath, int api_data_head(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
cxobj **xtop, cxobj ***xvec, size_t *xlen); cvec *qvec);
int xmldb_put_rpc(clicon_handle h, char *db, cxobj *xt, enum operation_type op); int api_data_get(clicon_handle h, FCGX_Request *r, cvec *pcvec, int pi,
int xmldb_put_xkey_rpc(clicon_handle h, char *db, char *xk, char *val, cvec *qvec);
enum operation_type op); int api_data_post(clicon_handle h, FCGX_Request *r, char *api_path,
int xmldb_copy_rpc(clicon_handle h, char *from, char *to); cvec *pcvec, int pi,
int xmldb_lock_rpc(clicon_handle h, char *db, int pid); cvec *qvec, char *data);
int xmldb_unlock_rpc(clicon_handle h, char *db, int pid); int api_data_put(clicon_handle h, FCGX_Request *r, char *api_path,
int xmldb_islocked_rpc(clicon_handle h, char *db); 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); #endif /* _RESTCONF_METHODS_H_ */
int xmldb_delete_rpc(clicon_handle h, char *db);
int xmldb_init_rpc(clicon_handle h, char *db);
#endif /* _CLIXON_XML_DB_RPC_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 # Directory where "running", "candidate" and "startup" are placed
CLICON_XMLDB_DIR localstatedir/APPNAME 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 # Dont include keys in cvec in cli vars callbacks, ie a & k in 'a <b> k <c>' ignored
# CLICON_CLI_VARONLY 1 # CLICON_CLI_VARONLY 1
@ -138,15 +128,3 @@ CLICON_XMLDB_DIR localstatedir/APPNAME
# Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock; # Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;
CLICON_RESTCONF_PATH /www-data/fastcgi_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 cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure # This file is a shell script that caches the results of configure
@ -5018,7 +5018,6 @@ do
"apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;; "apps/netconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/netconf/Makefile" ;;
"apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;; "apps/restconf/Makefile") CONFIG_FILES="$CONFIG_FILES apps/restconf/Makefile" ;;
"apps/dbctrl/Makefile") CONFIG_FILES="$CONFIG_FILES apps/dbctrl/Makefile" ;; "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" ;; "include/Makefile") CONFIG_FILES="$CONFIG_FILES include/Makefile" ;;
"etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;; "etc/Makefile") CONFIG_FILES="$CONFIG_FILES etc/Makefile" ;;
"etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;; "etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;;

View file

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

View file

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

View file

@ -91,6 +91,7 @@ Q: How do you change the example?
- routing_backend.c - Commit and validate functions. - routing_backend.c - Commit and validate functions.
- routing_netconf.c - Modify semantics of netconf commands. - routing_netconf.c - Modify semantics of netconf commands.
Q: How do you check what is in a database? 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 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? Q: How do you use netconf?
-------------------------- --------------------------
As an alternative to cli configuration, you can use netconf As an alternative to cli configuration, you can use netconf. Easiest is to just pipe netconf commands to the clixon_netconf application.
directly. Easiest is to just pipe netconf commands to the
clixon_netconf application.
Example: Example:
echo "<rpc><get-config><source><candidate/></source><configuration/></get-config></rpc>]]>]]>" | clixon_netconf -f /usr/local/etc/routing.conf 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 1. Setting data example using netconf
------------------------------------- -------------------------------------
<rpc><edit-config><target><candidate/></target><config> <rpc><edit-config><target><candidate/></target><config>
<interfaces> <interfaces>
<interface> <interface>
@ -45,7 +44,38 @@ clixon_netconf -f /usr/local/etc/routing.conf
<rpc><validate><source><candidate/></source></validate></rpc>]]>]]> <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 cd docker
# look in README # look in README

View file

@ -19,18 +19,11 @@ CLICON_CLI_GENMODEL_COMPLETION 1
# CLICON_CLI_GENMODEL_TYPE VARS # CLICON_CLI_GENMODEL_TYPE VARS
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 # 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 # 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"); # E.g cmd, callback("single arg"); -> cmd, callback("two" "args");
# And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files # And change predefined callbacks, eg cli_commit -> cli_commitv in all cli files
CLICON_CLIGEN_CALLBACK_SINGLE_ARG 0 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 <errno.h>
#include <signal.h> #include <signal.h>
#include <unistd.h> #include <unistd.h>
#include <sys/time.h>
/* clicon */ /* clicon */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -53,13 +53,16 @@
/* These include signatures for plugin and transaction callbacks. */ /* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h> #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 /*! This is called on validate (and commit). Check validity of candidate
*/ */
int int
transaction_validate(clicon_handle h, transaction_validate(clicon_handle h,
transaction_data td) transaction_data td)
{ {
transaction_print(stderr, td); // transaction_print(stderr, td);
return 0; return 0;
} }
@ -70,20 +73,62 @@ transaction_commit(clicon_handle h,
transaction_data td) transaction_data td)
{ {
cxobj *target = transaction_target(td); /* wanted XML tree */ cxobj *target = transaction_target(td); /* wanted XML tree */
cxobj **vec; cxobj **vec = NULL;
int i; int i;
size_t len; size_t len;
/* Get all added i/fs */ /* Get all added i/fs */
if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0) if (xpath_vec_flag(target, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
return -1; return -1;
if (debug)
for (i=0; i<len; i++) /* Loop over added i/fs */ for (i=0; i<len; i++) /* Loop over added i/fs */
xml_print(stdout, vec[i]); /* Print the added interface */ xml_print(stdout, vec[i]); /* Print the added interface */
if (vec)
free(vec);
return 0; 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 * Plugin initialization
*/ */
@ -92,8 +137,15 @@ plugin_init(clicon_handle h)
{ {
int retval = -1; 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; retval = 0;
// done: done:
return retval; return retval;
} }

View file

@ -71,7 +71,7 @@ int
mycallback(clicon_handle h, cvec *cvv, cvec *argv) mycallback(clicon_handle h, cvec *cvv, cvec *argv)
{ {
int retval = -1; int retval = -1;
cxobj *xt = NULL; cxobj *xret = NULL;
cg_var *myvar; cg_var *myvar;
/* Access cligen callback variables */ /* 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 */ cli_output(stderr, "arg = %s\n", cv_string_get(cvec_i(argv,0))); /* get string value */
/* Show eth0 interfaces config using XPATH */ /* Show eth0 interfaces config using XPATH */
if (xmldb_get(h, "candidate", if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]",
"/interfaces/interface[name=eth0]", &xret) < 0)
&xt, NULL, NULL) < 0)
goto done; goto done;
xml_print(stdout, xt); xml_print(stdout, xret);
retval = 0; retval = 0;
done: done:
if (xt) if (xret)
xml_free(xt); 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; return retval;
} }

View file

@ -4,42 +4,53 @@ CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="routing_cli"; CLICON_PLUGIN="routing_cli";
# Note, when switching to PT, change datamodel to only @datamodel # 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-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(); validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commitv(); commit("Commit the changes"), cli_commit();
quit("Quit Hello"), cli_quitv(); quit("Quit Hello"), cli_quit();
delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_allv("candidate"); delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate");
startup("Store running as startup config"), db_copy("running", "startup"); 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); no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cli((int32)0);
debug("Debugging parts of the system"), cli_debug_cliv((int32)1);{ 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_cliv(); level("Set debug level: 1..n") <level:int32>("Set debug level (0..n)"), cli_debug_backend();
} }
copy("Copy and create a new object") {
discard("Discard edits (rollback 0)"), discard_changesv(); 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"){ show("Show a particular state of the system"){
xpath("Show configuration") <xpath:string>("XPATH expression"), show_confv_xpath("candidate","/"); xpath("Show configuration") <xpath:string>("XPATH expression"), show_conf_xpath("candidate");
compare("Compare candidate and running databases"), compare_dbsv((int32)0);{ compare("Compare candidate and running databases"), compare_dbs((int32)0);{
xml("Show comparison in xml"), compare_dbsv((int32)0); xml("Show comparison in xml"), compare_dbs((int32)0);
text("Show comparison in text"), compare_dbsv((int32)1); text("Show comparison in text"), compare_dbs((int32)1);
} }
configuration("Show configuration"), show_confv_as_text("candidate","/");{ configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
xml("Show configuration as XML"), show_confv_as_xml("candidate","/"); xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");
netconf("Show configuration as netconf edit-config operation"), show_confv_as_netconf("candidate","/"); netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");
text("Show configuration as text"), show_confv_as_text("candidate","/"); text("Show configuration as text"), cli_show_config("candidate","text","/");
cli("Show configuration as cli commands"), show_confv_as_cli("candidate","/"); cli("Show configuration as cli commands"), cli_show_config("candidate", "cli", "/");
json("Show configuration as cli commands"), show_confv_as_json("candidate","/"); 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"); 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_filev("filename","replace");{ load("Load configuration from XML file") <filename:string>("Filename (local filename)"),load_config_file("filename", "replace");{
replace("Replace candidate with file contents"), load_config_filev("filename","replace"); replace("Replace candidate with file contents"), load_config_file("filename", "replace");
merge("Merge file with existent candidate"), load_config_filev("filename","merge"); merge("Merge file with existent candidate"), load_config_file("filename", "merge");
} }
example("This is a comment") <var:int32>("Just a random number"), mycallback("myarg"); 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_ #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, int clicon_file_dirent(const char *dir, struct dirent **ent,
const char *regexp, mode_t type, const char *label); const char *regexp, mode_t type, const char *label);

View file

@ -50,9 +50,7 @@
* Types * Types
*/ */
/* /*! Controls how keywords a generated in CLI syntax / prints from object model
* enum gensyntx
* Controls how keywords a generated in CLI syntax / prints from obhect model
* Example syntax a.b[] $!x $y: * Example syntax a.b[] $!x $y:
* NONE: a b <x> <y>; * NONE: a b <x> <y>;
* VARS: a b <x> y <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); int clicon_cli_genmodel_completion(clicon_handle h);
char *clicon_xmldb_dir(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); char *clicon_quiet_mode(clicon_handle h);
enum genmodel_type clicon_cli_genmodel_type(clicon_handle h); enum genmodel_type clicon_cli_genmodel_type(clicon_handle h);

View file

@ -42,136 +42,51 @@
* Types * Types
*/ */
enum format_enum{ enum format_enum{
MSG_NOTIFY_TXT, /* means filter works on strings */ FORMAT_XML,
MSG_NOTIFY_XML, /* means filter works on xml */ FORMAT_JSON,
}; FORMAT_TEXT,
FORMAT_CLI,
/* See also map_type2str in clicon_proto.c */ FORMAT_NETCONF
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
*/
}; };
/* Protocol message header */ /* Protocol message header */
struct clicon_msg { struct clicon_msg {
uint16_t op_len; /* length of message. */ 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 */ 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 * 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_connect_unix(char *sockpath);
int clicon_rpc_connect_unix(struct clicon_msg *msg, int clicon_rpc_connect_unix(struct clicon_msg *msg,
char *sockpath, char *sockpath,
char **data, char **ret,
uint16_t *datalen, int *sock0);
int *sock0,
const char *label);
int clicon_rpc_connect_inet(struct clicon_msg *msg, int clicon_rpc_connect_inet(struct clicon_msg *msg,
char *dst, char *dst,
uint16_t port, uint16_t port,
char **data, char **ret,
uint16_t *datalen, int *sock0);
int *sock0,
const char *label);
int clicon_rpc(int s, struct clicon_msg *msg, char **data, uint16_t *datalen, int clicon_rpc(int s, struct clicon_msg *msg, char **xret);
const char *label);
#endif
int clicon_msg_send(int s, struct clicon_msg *msg); int clicon_msg_send(int s, struct clicon_msg *msg);
int clicon_msg_rcv(int s, struct clicon_msg **msg, int clicon_msg_rcv(int s, struct clicon_msg **msg, int *eof);
int *eof, const char *label);
int send_msg_notify(int s, int level, char *event); 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 detect_endtag(char *tag, char ch, int *state);
int send_msg_err(int s, int err, int suberr, char *format, ...);
#endif /* _CLIXON_PROTO_H_ */ #endif /* _CLIXON_PROTO_H_ */

View file

@ -40,27 +40,25 @@
#ifndef _CLIXON_PROTO_CLIENT_H_ #ifndef _CLIXON_PROTO_CLIENT_H_
#define _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 clicon_rpc_validate(clicon_handle h, char *db); int *sock0);
int clicon_rpc_change(clicon_handle h, char *db, int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp);
enum operation_type op, char *key, char *val); int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr);
int clicon_rpc_xmlput(clicon_handle h, char *db, enum operation_type op, 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); char *api_path, char *xml);
int clicon_rpc_dbitems(clicon_handle h, char *db, char *rx, int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2);
char *attr, char *val, int clicon_rpc_delete_config(clicon_handle h, char *db);
cvec ***cvv, size_t *cvvlen); int clicon_rpc_lock(clicon_handle h, char *db);
int clicon_rpc_save(clicon_handle h, char *dbname, int snapshot, char *filename); int clicon_rpc_unlock(clicon_handle h, char *db);
int clicon_rpc_load(clicon_handle h, int replace, char *db, char *filename); int clicon_rpc_close_session(clicon_handle h);
int clicon_rpc_copy(clicon_handle h, char *db1, char *db2); int clicon_rpc_kill_session(clicon_handle h, int session_id);
int clicon_rpc_kill(clicon_handle h, int session_id); int clicon_rpc_validate(clicon_handle h, char *db);
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_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_ */ #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 * Prototypes
*/ */
char **clicon_sepsplit (char *string, char *delim, int *nvec, const char *label); char **clicon_strsep(char *string, char *delim, int *nvec0);
char **clicon_strsplit (char *string, char *delim, int *nvec, const char *label); char *clicon_strjoin (int argc, char **argv, char *delim);
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);
#ifndef HAVE_STRNDUP #ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t); char *clicon_strndup (const char *, size_t);
#endif /* ! HAVE_STRNDUP */ #endif /* ! HAVE_STRNDUP */
int clicon_strmatch(const char *str, const char *regexp, char **match);
char *clicon_strsub(char *str, char *from, char *to);
#endif /* _CLIXON_STRING_H_ */ #endif /* _CLIXON_STRING_H_ */

View file

@ -40,7 +40,7 @@
* Types * Types
*/ */
/* Netconf operation type */ /* Netconf operation type */
enum operation_type{ /* edit-config */ enum operation_type{ /* edit-configo */
OP_MERGE, /* merge config-data */ OP_MERGE, /* merge config-data */
OP_REPLACE,/* replace or create config-data */ OP_REPLACE,/* replace or create config-data */
OP_CREATE, /* create config data, error if exist */ 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_i_set(cxobj *xt, int i, cxobj *xc);
cxobj *xml_child_each(cxobj *xparent, cxobj *xprev, enum cxobj_type type); 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); int xml_childvec_set(cxobj *x, int len);
cxobj *xml_new(char *name, cxobj *xn_parent); cxobj *xml_new(char *name, cxobj *xn_parent);
cxobj *xml_new_spec(char *name, cxobj *xn_parent, void *spec); cxobj *xml_new_spec(char *name, cxobj *xn_parent, void *spec);
@ -124,6 +125,7 @@ int clicon_xml_parse_file(int fd, cxobj **xml_top, char *endtag);
/* XXX obsolete */ /* XXX obsolete */
#define clicon_xml_parse_string(str, x) clicon_xml_parse_str((*str), x) #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_str(char *str, cxobj **xml_top);
int clicon_xml_parse(cxobj **cxtop, char *format, ...);
int xml_copy(cxobj *x0, cxobj *x1); int xml_copy(cxobj *x0, cxobj *x1);
cxobj *xml_dup(cxobj *x0); 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_parse(cxobj *xb, enum cv_type type, cg_var **cvp);
int xml_body_int32(cxobj *xb, int32_t *val); int xml_body_int32(cxobj *xb, int32_t *val);
int xml_body_uint32(cxobj *xb, uint32_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 */ #endif /* _CLIXON_XML_H */

View file

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

View file

@ -53,7 +53,7 @@ enum {
* Prototypes * Prototypes
*/ */
int xml2txt(FILE *f, cxobj *x, int level); 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 xml_yang_validate(cxobj *xt, yang_stmt *ys) ;
int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0);
int cvec2xml_1(cvec *cvv, char *toptag, cxobj *xp, cxobj **xt0); 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_json.c \
clixon_yang.c clixon_yang_type.c \ clixon_yang.c clixon_yang_type.c \
clixon_hash.c clixon_options.c clixon_plugin.c \ clixon_hash.c clixon_options.c clixon_plugin.c \
clixon_proto.c clixon_proto_encode.c clixon_proto_client.c \ clixon_proto.c clixon_proto_client.c \
clixon_xsl.c clixon_sha1.c clixon_xml_db.c clixon_xml_db_rpc.c clixon_xsl.c clixon_sha1.c clixon_xml_db.c
YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \ YACCOBJS := lex.clixon_xml_parse.o clixon_xml_parse.tab.o \
lex.clixon_yang_parse.o clixon_yang_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}, {"UNIX error", OE_UNIX},
{"Syslog error", OE_SYSLOG}, {"Syslog error", OE_SYSLOG},
{"Routing demon error", OE_ROUTING}, {"Routing demon error", OE_ROUTING},
{"XML error", OE_XML},
{"Plugins", OE_PLUGIN}, {"Plugins", OE_PLUGIN},
{"Yang error", OE_YANG}, {"Yang error", OE_YANG},
{"FATAL", OE_FATAL}, {"FATAL", OE_FATAL},

View file

@ -62,99 +62,6 @@
#include "clixon_string.h" #include "clixon_string.h"
#include "clixon_file.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 * 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) json_scan_exit(struct clicon_json_yacc_arg *jy)
{ {
yy_delete_buffer(jy->jy_lexbuf); yy_delete_buffer(jy->jy_lexbuf);
#if defined(YY_FLEX_SUBMINOR_VERSION) && YY_FLEX_SUBMINOR_VERSION >= 9
clixon_json_parselex_destroy(); /* modern */ clixon_json_parselex_destroy(); /* modern */
#else
yy_init = 1; /* This does not quite free all buffers */
#endif
return 0; return 0;
} }

View file

@ -224,7 +224,7 @@ json_current_body(struct clicon_json_yacc_arg *jy,
*/ */
/* top: json -> value is also possible */ /* 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");} 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) if (hash_add(copt, "CLICON_CLI_GENMODEL_COMPLETION", "0", strlen("0")+1) < 0)
goto catch; 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; retval = 0;
catch: catch:
unchunk_group(__FUNCTION__); unchunk_group(__FUNCTION__);
@ -616,35 +612,6 @@ clicon_xmldb_dir(clicon_handle h)
return clicon_option_str(h, "CLICON_XMLDB_DIR"); 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 /*! Get YANG specification
* Must use hash functions directly since they are not strings. * Must use hash functions directly since they are not strings.
*/ */

View file

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

View file

@ -68,47 +68,117 @@
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h" #include "clixon_chunk.h"
#include "clixon_sig.h" #include "clixon_sig.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_proto.h" #include "clixon_proto.h"
#include "clixon_proto_encode.h"
static int _atomicio_sig = 0; static int _atomicio_sig = 0;
struct map_type2str{ /*! Formats (showas) derived from XML
enum clicon_msg_type mt_type; */
char *mt_str; /* string as in 4.2.4 in RFC 6020 */ struct formatvec{
char *fv_str;
int fv_int;
}; };
/* Mapping between yang keyword string <--> clicon constants */ static struct formatvec _FORMATS[] = {
static const struct map_type2str msgmap[] = { {"xml", FORMAT_XML},
{CLICON_MSG_COMMIT, "commit"}, {"text", FORMAT_TEXT},
{CLICON_MSG_VALIDATE, "validate"}, {"json", FORMAT_JSON},
{CLICON_MSG_CHANGE, "change"}, {"cli", FORMAT_CLI},
{CLICON_MSG_XMLPUT, "xmlput"}, {"netconf", FORMAT_NETCONF},
{CLICON_MSG_SAVE, "save"}, {NULL, -1}
{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 char * /*! Translate from numeric format to string representation
msg_type2str(enum clicon_msg_type type) * @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++) for (fv=_FORMATS; fv->fv_int != -1; fv++)
if (mt->mt_type == type) if (fv->fv_int == showas)
return mt->mt_str; 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; 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 /*! Open local connection using unix domain sockets
* @param[in] sockpath Unix domain file path
* @retval s socket
* @retval -1 error
*/ */
int int
clicon_connect_unix(char *sockpath) clicon_connect_unix(char *sockpath)
@ -147,13 +217,19 @@ atomicio_sig_handler(int arg)
_atomicio_sig++; _atomicio_sig++;
} }
/*! Ensure all of data on socket comes through. fn is either read or write /*! 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 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; ssize_t res, pos = 0;
while (n > pos) { while (n > pos) {
@ -177,6 +253,9 @@ atomicio(ssize_t (*fn) (int, void *, size_t), int fd, void *_s, size_t n)
return (pos); return (pos);
} }
/*! Print message on debug. Log if syslog, stderr if not
* @param[in] msg CLICON msg
*/
static int static int
msg_dump(struct clicon_msg *msg) msg_dump(struct clicon_msg *msg)
{ {
@ -202,14 +281,18 @@ msg_dump(struct clicon_msg *msg)
return 0; 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 int
clicon_msg_send(int s, clicon_msg_send(int s,
struct clicon_msg *msg) struct clicon_msg *msg)
{ {
int retval = -1; int retval = -1;
clicon_debug(2, "%s: send msg seq=%d len=%d", clicon_debug(2, "%s: send msg len=%d",
__FUNCTION__, ntohs(msg->op_type), ntohs(msg->op_len)); __FUNCTION__, ntohs(msg->op_len));
if (debug > 2) if (debug > 2)
msg_dump(msg); msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write, 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? * XXX: timeout? and signals?
* There is rudimentary code for turning on signals and handling them * There is rudimentary code for turning on signals and handling them
@ -233,18 +316,15 @@ clicon_msg_send(int s,
* behaviour. * behaviour.
* Now, ^C will interrupt the whole process, and this may not be what you want. * 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[in] s socket (unix or inet) to communicate with backend
* @param[out] msg CLICON msg data reply structure. allocated using CLICON chunks, * @param[out] msg CLICON msg data reply structure. Free with free()
* freed by caller with unchunk*(...,label)
* @param[out] eof Set if eof encountered * @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. * Note: caller must ensure that s is closed if eof is set after call.
*/ */
int int
clicon_msg_rcv(int s, clicon_msg_rcv(int s,
struct clicon_msg **msg, struct clicon_msg **msg,
int *eof, int *eof)
const char *label)
{ {
int retval = -1; int retval = -1;
struct clicon_msg hdr; struct clicon_msg hdr;
@ -271,10 +351,10 @@ clicon_msg_rcv(int s,
goto done; goto done;
} }
mlen = ntohs(hdr.op_len); mlen = ntohs(hdr.op_len);
clicon_debug(2, "%s: rcv msg seq=%d, len=%d", clicon_debug(2, "%s: rcv msg len=%d",
__FUNCTION__, ntohs(hdr.op_type), mlen); __FUNCTION__, mlen);
if ((*msg = (struct clicon_msg *)chunk(mlen, label)) == NULL){ if ((*msg = (struct clicon_msg *)malloc(mlen)) == NULL){
clicon_err(OE_CFG, errno, "%s: chunk", __FUNCTION__); clicon_err(OE_CFG, errno, "malloc");
goto done; goto done;
} }
memcpy(*msg, &hdr, hlen); memcpy(*msg, &hdr, hlen);
@ -295,25 +375,27 @@ clicon_msg_rcv(int s,
return retval; return retval;
} }
/*! Connect to server, send a clicon_msg message and wait for result using unix socket
/*! 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. * @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* NOTE: this is dependent on unix domain * @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 int
clicon_rpc_connect_unix(struct clicon_msg *msg, clicon_rpc_connect_unix(struct clicon_msg *msg,
char *sockpath, char *sockpath,
char **data, char **retdata,
uint16_t *datalen, int *sock0)
int *sock0,
const char *label)
{ {
int retval = -1; int retval = -1;
int s = -1; int s = -1;
struct stat sb; struct stat sb;
clicon_debug(1, "Send %s msg on %s", clicon_debug(1, "Send msg on %s", sockpath);
msg_type2str(ntohs(msg->op_type)), sockpath);
/* special error handling to get understandable messages (otherwise ENOENT) */ /* special error handling to get understandable messages (otherwise ENOENT) */
if (stat(sockpath, &sb) < 0){ if (stat(sockpath, &sb) < 0){
clicon_err(OE_PROTO, errno, "%s: config daemon not running?", sockpath); 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) if ((s = clicon_connect_unix(sockpath)) < 0)
goto done; goto done;
if (clicon_rpc(s, msg, data, datalen, label) < 0) if (clicon_rpc(s, msg, retdata) < 0)
goto done; goto done;
if (sock0 != NULL) if (sock0 != NULL)
*sock0 = s; *sock0 = s;
@ -336,24 +418,29 @@ clicon_rpc_connect_unix(struct clicon_msg *msg,
return retval; return retval;
} }
/*! Connect to server, send an clicon_msg message and wait for result using an inet socket /*! Connect to server, send a 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. * 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 int
clicon_rpc_connect_inet(struct clicon_msg *msg, clicon_rpc_connect_inet(struct clicon_msg *msg,
char *dst, char *dst,
uint16_t port, uint16_t port,
char **data, char **retdata,
uint16_t *datalen, int *sock0)
int *sock0,
const char *label)
{ {
int retval = -1; int retval = -1;
int s = -1; int s = -1;
struct sockaddr_in addr; struct sockaddr_in addr;
clicon_debug(1, "Send %s msg to %s:%hu", clicon_debug(1, "Send msg to %s:%hu", dst, port);
msg_type2str(ntohs(msg->op_type)), dst, port);
memset(&addr, 0, sizeof(addr)); memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
@ -371,7 +458,7 @@ clicon_rpc_connect_inet(struct clicon_msg *msg,
close(s); close(s);
goto done; goto done;
} }
if (clicon_rpc(s, msg, data, datalen, label) < 0) if (clicon_rpc(s, msg, retdata) < 0)
goto done; goto done;
if (sock0 != NULL) if (sock0 != NULL)
*sock0 = s; *sock0 = s;
@ -391,29 +478,24 @@ clicon_rpc_connect_inet(struct clicon_msg *msg,
* *
* @param[in] s Socket to communicate with backend * @param[in] s Socket to communicate with backend
* @param[in] msg CLICON msg data structure. It has fixed header and variable body. * @param[in] msg CLICON msg data structure. It has fixed header and variable body.
* @param[out] data Returned data as byte-strin exclusing header. * @param[out] xret Returned data as netconf xml tree.
* Deallocate w unchunk...(..., label) * @retval 0 OK
* @param[out] datalen Length of returned data * @retval -1 Error
* @param[in] label Label used in chunk allocation.
*/ */
int int
clicon_rpc(int s, clicon_rpc(int s,
struct clicon_msg *msg, struct clicon_msg *msg,
char **data, char **ret)
uint16_t *datalen,
const char *label)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *reply; struct clicon_msg *reply;
int eof; int eof;
uint32_t err; char *data = NULL;
uint32_t suberr; cxobj *cx = NULL;
char *reason;
enum clicon_msg_type type;
if (clicon_msg_send(s, msg) < 0) if (clicon_msg_send(s, msg) < 0)
goto done; goto done;
if (clicon_msg_rcv(s, &reply, &eof, label) < 0) if (clicon_msg_rcv(s, &reply, &eof) < 0)
goto done; goto done;
if (eof){ if (eof){
clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__); clicon_err(OE_PROTO, ESHUTDOWN, "%s: Socket unexpected close", __FUNCTION__);
@ -421,37 +503,31 @@ clicon_rpc(int s,
errno = ESHUTDOWN; errno = ESHUTDOWN;
goto done; goto done;
} }
type = ntohs(reply->op_type); data = reply->op_body; /* assume string */
switch (type){ if (ret && data)
case CLICON_MSG_OK: if ((*ret = strdup(data)) == NULL){
if (data != NULL) { clicon_err(OE_UNIX, errno, "strdup");
*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)
goto done; 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; retval = 0;
done: done:
if (cx)
xml_free(cx);
if (reply)
free(reply);
return retval; 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 int
send_msg_reply(int s, send_msg_reply(int s,
uint16_t type,
char *data, char *data,
uint16_t datalen) uint16_t datalen)
{ {
@ -463,7 +539,6 @@ send_msg_reply(int s,
if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL) if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL)
goto done; goto done;
memset(reply, 0, len); memset(reply, 0, len);
reply->op_type = htons(type);
reply->op_len = htons(len); reply->op_len = htons(len);
if (datalen > 0) if (datalen > 0)
memcpy(reply->op_body, data, datalen); memcpy(reply->op_body, data, datalen);
@ -475,57 +550,55 @@ send_msg_reply(int s,
return retval; return retval;
} }
int /*! Send a clicon_msg NOTIFY message asynchronously to client
send_msg_ok(int s) *
{ * @param[in] s Socket to communicate with client
return send_msg_reply(s, CLICON_MSG_OK, NULL, 0); * @param[in] level
} * @param[in] event
* @retval 0 OK
* @retval -1 Error
*/
int int
send_msg_notify(int s, send_msg_notify(int s,
int level, int level,
char *event) char *event)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *msg; 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; goto done;
if (clicon_msg_send(s, msg) < 0) if (clicon_msg_send(s, msg) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (msg)
free(msg);
return retval; 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 int
send_msg_err(int s, int err, int suberr, char *format, ...) detect_endtag(char *tag,
char ch,
int *state)
{ {
va_list args; int retval = 0;
char *reason;
int len;
int retval = -1;
struct clicon_msg *msg;
va_start(args, format); if (tag[*state] == ch){
len = vsnprintf(NULL, 0, format, args) + 1; (*state)++;
va_end(args); if (*state == strlen(tag)){
if ((reason = (char *)chunk(len, __FUNCTION__)) == NULL) *state = 0;
return -1; retval = 1;
memset(reason, 0, len); }
va_start(args, format); }
vsnprintf(reason, len, format, args); else
va_end(args); *state = 0;
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__);
return retval; return retval;
} }

View file

@ -49,6 +49,7 @@
#include <unistd.h> #include <unistd.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/syslog.h>
/* cligen */ /* cligen */
#include <cligen/cligen.h> #include <cligen/cligen.h>
@ -56,38 +57,36 @@
/* clicon */ /* clicon */
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_chunk.h" #include "clixon_chunk.h"
#include "clixon_log.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_options.h" #include "clixon_options.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_proto.h" #include "clixon_proto.h"
#include "clixon_err.h" #include "clixon_err.h"
#include "clixon_xml.h"
#include "clixon_proto_encode.h"
#include "clixon_proto_client.h" #include "clixon_proto_client.h"
/*! Internal rpc function /*! Send internal netconf rpc from client to backend
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] msg Encoded message * @param[in] msg Encoded message. Deallocate woth free
* @param[out] ret Return value from backend server (reply) * @param[out] xret Return value from backend as netconf xml tree. Free w xml_free
* @param[out] retlen Length of return value
* @param[inout] sock0 If pointer exists, do not close socket to backend on success * @param[inout] sock0 If pointer exists, do not close socket to backend on success
* and return it here. For keeping a notify socket open * 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 * Note: sock0 is if connection should be persistent, like a notification/subscribe api
*/ */
static int int
clicon_rpc_msg(clicon_handle h, clicon_rpc_msg(clicon_handle h,
struct clicon_msg *msg, struct clicon_msg *msg,
char **ret, cxobj **xret0,
uint16_t *retlen, int *sock0)
int *sock0,
const char *label)
{ {
int retval = -1; int retval = -1;
char *sock; char *sock;
int port; int port;
char *retdata = NULL;
cxobj *xret = NULL;
if ((sock = clicon_sock(h)) == NULL){ if ((sock = clicon_sock(h)) == NULL){
clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set"); 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? */ /* What to do if inet socket? */
switch (clicon_sock_family(h)){ switch (clicon_sock_family(h)){
case AF_UNIX: 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 0
if (errno == ESHUTDOWN) if (errno == ESHUTDOWN)
/* Maybe could reconnect on a higher layer, but lets fail /* Maybe could reconnect on a higher layer, but lets fail
@ -115,184 +114,233 @@ clicon_rpc_msg(clicon_handle h,
clicon_err(OE_FATAL, 0, "CLICON_SOCK_PORT not set"); clicon_err(OE_FATAL, 0, "CLICON_SOCK_PORT not set");
goto done; 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; goto done;
break; 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; retval = 0;
done: done:
if (retdata)
free(retdata);
if (xret)
xml_free(xret);
return retval; return retval;
} }
/*! Commit changes send a commit request to backend daemon /*! Generic xml netconf clicon rpc
* @param[in] h CLICON handle * Want to go over to use netconf directly between client and server,...
* @param[in] from name of 'from' database (eg "candidate") * @param[in] h clicon handle
* @param[in] db name of 'to' database (eg "running") * @param[in] xmlstr XML netconf tree as string
* @retval 0 Copy current->candidate * @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 int
clicon_rpc_commit(clicon_handle h, clicon_rpc_netconf(clicon_handle h,
char *from, char *xmlstr,
char *to) cxobj **xret,
int *sp)
{ {
int retval = -1; 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; goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0) if (clicon_rpc_msg(h, msg, xret, sp) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (msg)
free(msg);
return retval; return retval;
} }
/*! Send validate request to backend daemon /*! Generic xml netconf clicon rpc
* @param[in] h CLICON handle * Want to go over to use netconf directly between client and server,...
* @param[in] db Name of database * @param[in] h clicon handle
* @retval 0 * @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 int
clicon_rpc_validate(clicon_handle h, clicon_rpc_netconf_xml(clicon_handle h,
char *db) cxobj *xml,
cxobj **xret,
int *sp)
{ {
int retval = -1; 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; 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; goto done;
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (cb)
cbuf_free(cb);
return retval; return retval;
} }
/*! Send database change request to backend daemon, variant for xmldb /*! Generate clicon error function call from Netconf error message
* Same as clicon_proto_change just with a string * @param[in] xerr Netconf error message on the level: <rpc-reply><rpc-error>
*/
int
clicon_rpc_generate_error(cxobj *xerr)
{
int retval = -1;
cbuf *cb = NULL;
cxobj *x;
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
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] h CLICON handle
* @param[in] db Name of database * @param[in] db Name of database
* @param[in] op Operation on database item: set, delete, (merge?) * @param[in] xpath XPath (or "")
* @param[in] key Database key * @param[out] xt XML tree. must be freed by caller with xml_free
* @param[in] value value as string
* @retval 0 OK * @retval 0 OK
* @retval -1 Error * @retval -1 Error, fatal or xml
* @note special case: remove all: key:"/" op:OP_REMOVE * @code
* cxobj *xt = NULL;
* if (clicon_rpc_get_config(h, "running", "/", &xt) < 0)
* err;
* if (xt)
* xml_free(xt);
* @endcode
*/ */
int int
clicon_rpc_change(clicon_handle h, clicon_rpc_get_config(clicon_handle h,
char *db, char *db,
enum operation_type op, char *xpath,
char *key, cxobj **xt)
char *val)
{ {
int retval = -1; int retval = -1;
struct clicon_msg *msg; struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
cxobj *xret = NULL;
cxobj *xerr;
cxobj *xd;
if ((msg = clicon_msg_change_encode(db, if ((cb = cbuf_new()) == NULL)
op,
key,
val,
val?strlen(val)+1:0,
__FUNCTION__)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0) 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; 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; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (cb)
cbuf_free(cb);
if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval; return retval;
} }
/*! Send database entries as XML to backend daemon /*! 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] h CLICON handle
* @param[in] db Name of database * @param[in] db Name of database
* @param[in] op Operation on database item: OP_MERGE, OP_REPLACE * @param[in] op Operation on database item: OP_MERGE, OP_REPLACE
* @param[in] api_path restconf API Path (or "") * @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 0 OK
* @retval -1 Error * @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 int
clicon_rpc_xmlput(clicon_handle h, clicon_rpc_edit_config(clicon_handle h,
char *db, char *db,
enum operation_type op, enum operation_type op,
char *api_path, char *api_path,
char *xml) char *xmlstr)
{ {
int retval = -1; 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, if ((cb = cbuf_new()) == NULL)
(uint32_t)op,
api_path,
xml,
__FUNCTION__)) == NULL)
goto done; 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; 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; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (xret)
return retval; xml_free(xret);
} if (cb)
cbuf_free(cb);
/*! Send database load request to backend daemon if (msg)
* @param[in] h CLICON handle free(msg);
* @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__);
return retval; return retval;
} }
@ -300,46 +348,319 @@ clicon_rpc_load(clicon_handle h,
* Note this assumes the backend can access these files and (usually) assumes * Note this assumes the backend can access these files and (usually) assumes
* clients and servers have the access to the same filesystem. * clients and servers have the access to the same filesystem.
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] db1 src database, eg "candidate" * @param[in] db1 src database, eg "running"
* @param[in] db2 dst database, eg "running" * @param[in] db2 dst database, eg "startup"
* @code
* if (clicon_rpc_copy_config(h, "running", "startup") < 0)
* err;
* @endcode
*/ */
int int
clicon_rpc_copy(clicon_handle h, clicon_rpc_copy_config(clicon_handle h,
char *db1, char *db1,
char *db2) char *db2)
{ {
int retval = -1; 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; goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (xret)
xml_free(xret);
if (msg)
free(msg);
return retval; return retval;
} }
/*! Send a request to backend to delete a config database
/*! Send a kill session request to backend server
* @param[in] h CLICON handle * @param[in] h CLICON handle
* @param[in] session_id Id of session to kill * @param[in] db database, eg "running"
* @code
* if (clicon_rpc_delete_config(h, "startup") < 0)
* err;
* @endcode
*/ */
int int
clicon_rpc_kill(clicon_handle h, clicon_rpc_delete_config(clicon_handle h,
char *db)
{
int retval = -1;
struct clicon_msg *msg = NULL;
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_encode("<rpc><delete-config><target><%s/></target></delete-config></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;
}
/*! 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 session_id)
{ {
int retval = -1; 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><kill-session><session-id>%d</session-id></kill-session></rpc>", session_id)) == NULL)
goto done; goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL, NULL, __FUNCTION__) < 0) if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done; goto done;
if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
clicon_rpc_generate_error(xerr);
goto done;
}
retval = 0; retval = 0;
done: done:
unchunk_group(__FUNCTION__); 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; return retval;
} }
@ -352,107 +673,28 @@ clicon_rpc_debug(clicon_handle h,
int level) int level)
{ {
int retval = -1; 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; 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; goto done;
retval = 0;
done:
unchunk_group(__FUNCTION__);
return retval;
} }
if (xpath_first(xret, "//rpc-reply/ok") == NULL){
/*! An rpc call from a frontend module to a function in a backend module clicon_err(OE_XML, 0, "rpc error"); /* XXX extract info from rpc-error */
*
* 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)
goto done; goto done;
if (clicon_rpc_msg(h, msg, (char**)ret, retlen, NULL, label) < 0)
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; retval = 0;
done: done:
unchunk_group(__FUNCTION__); if (msg)
free(msg);
if (xret)
xml_free(xret);
return retval; 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,130 +53,69 @@
#include "clixon_string.h" #include "clixon_string.h"
#include "clixon_err.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 * The given string is split into a vector where the delimiter can be
* any of the characters in the specified delimiter string. * 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 * by the caller
* *
* @param[in] string String to be split * @param[in] string String to be split
* @param[in] delim String of delimiter characters * @param[in] delim String of delimiter characters
* @param[out] nvec Number of entries in returned vector * @param[out] nvec Number of entries in returned vector
* @param[in] label Chunk label for returned vector * @retval vec Vector of strings. NULL terminated. Free after use
* @retval vec Vector of strings. Free with unchunk * @retval NULL Error *
* @retval NULL Error
* @see clicon_strsplit Operates on full string delimiters rather than
* individual character delimiters.
*/ */
char ** char **
clicon_sepsplit (char *string, clicon_strsep(char *string,
char *delim, char *delim,
int *nvec, int *nvec0)
const char *label)
{ {
int idx; char **vec = NULL;
size_t siz; char *ptr;
char *s, *s0; char *p;
char **vec, *vecp; int nvec = 1;
int i;
*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;
size_t siz; size_t siz;
char *s; char *s;
char **vec, *vecp; char *d;
*nvec = 1; if ((s = string)==NULL)
s = string; goto done;
while ((s = strstr(s, delim))) { while (*s){
s += strlen(delim); if ((d = index(delim, *s)) != NULL)
(*nvec)++; nvec++;
s++;
} }
/* alloc vector and append copy of string */
siz = ((*nvec +1) * sizeof (char *)) + strlen(string) + 1; siz = (nvec+1)* sizeof(char*) + strlen(string)+1;
vec = (char **) chunk (siz, label); if ((vec = (char**)malloc(siz)) == NULL){
if (!vec) { clicon_err(OE_UNIX, errno, "malloc");
return NULL; goto done;
} }
bzero (vec, siz); memset(vec, 0, siz);
ptr = (char*)vec + (nvec+1)* sizeof(char*); /* this is where ptr starts */
vecp = (char *)&vec[*nvec +1]; strncpy(ptr, string, strlen(string)+1);
bcopy (string, vecp, strlen (string)); i = 0;
while ((p = strsep(&ptr, delim)) != NULL)
s = vecp; vec[i++] = p;
for (idx = 0; idx < *nvec; idx++) { *nvec0 = nvec;
vec[idx] = s; done:
if ((s = strstr(s, delim)) != NULL) {
*s = '\0';
s += strlen(delim);
}
}
return vec; return vec;
} }
/*! Concatenate elements of a string array into a string. /*! Concatenate elements of a string array into a string.
* An optional delimiter string can be specified which will be inserted betwen * An optional delimiter string can be specified which will be inserted betwen
* each element. * each element.
* @param[in] label Chunk label for returned vector * @retval str Joined string. Free after use.
* @retval str Joined string. Free with unchunk()
* @retval NULL Failure * @retval NULL Failure
*/ */
char * char *
clicon_strjoin(int argc, clicon_strjoin(int argc,
char **argv, char **argv,
char *delim, char *delim)
const char *label)
{ {
int i; int i;
int len; int len;
@ -188,95 +127,17 @@ clicon_strjoin (int argc,
if (delim) if (delim)
len += (strlen(delim) * argc); len += (strlen(delim) * argc);
len += 1; /* '\0' */ len += 1; /* '\0' */
if ((str = malloc(len)) == NULL)
if ((str = chunk (len, label)) == NULL)
return NULL; return NULL;
memset (str, '\0', len); memset (str, '\0', len);
for (i = 0; i < argc; i++) { for (i = 0; i < argc; i++) {
if (i != 0) if (i != 0)
strncat (str, delim, len - strlen(str)); strncat (str, delim, len - strlen(str));
strncat (str, argv[i], len - strlen(str)); strncat (str, argv[i], len - strlen(str));
} }
return 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 len;
int retval = -1;
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';
}
*a0 = a;
*b0 = b;
retval = 0;
catch:
return retval;
}
/*! strndup() for systems without it, such as xBSD /*! strndup() for systems without it, such as xBSD
*/ */
@ -302,82 +163,48 @@ clicon_strndup (const char *str,
} }
#endif /* ! HAVE_STRNDUP */ #endif /* ! HAVE_STRNDUP */
/*! Match string against regexp. /*
* * Turn this on for uni-test programs
* If a match pointer is given, the matching substring * Usage: clixon_string join
* will be allocated 'match' will be pointing to it. The match string must * Example compile:
* be free:ed by the application. gcc -g -o clixon_string -I. -I../clixon ./clixon_string.c -lclixon -lcligen
* @retval -1 Failure * Example run:
* @retval 0 No match
* @retval >0 Match: Length of matching substring
*/ */
#if 0 /* Test program */
static int
usage(char *argv0)
{
fprintf(stderr, "usage:%s <string>\n", argv0);
exit(0);
}
int int
clicon_strmatch(const char *str, main(int argc, char **argv)
const char *regexp,
char **match)
{ {
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; int nvec;
char *new; char **vec;
char *retval = NULL; char *str0;
char *str1;
int i;
if ((vec = clicon_strsplit(str, from, &nvec, __FUNCTION__)) == NULL) { if (argc != 2){
clicon_err(OE_UNIX, errno, "Failed to split string"); usage(argv[0]);
goto done; return 0;
}
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;
} }
if ((new = clicon_strjoin (nvec, vec, to, __FUNCTION__)) == NULL) { #endif /* Test program */
clicon_err(OE_UNIX, errno, "Failed to split string");
goto done;
}
retval = strdup(new);
done:
unchunk_group(__FUNCTION__);
return retval;
}

View file

@ -465,6 +465,12 @@ xml_childvec_set(cxobj *x,
return 0; 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(). /*! Create new xml node given a name and parent. Free it with xml_free().
* *
* @param[in] name Name of new * @param[in] name Name of new
@ -913,11 +919,12 @@ clicon_xml2cbuf(cbuf *cb,
cprintf(cb, "%s:", xml_namespace(cx)); cprintf(cb, "%s:", xml_namespace(cx));
cprintf(cb, "%s", xml_name(cx)); cprintf(cb, "%s", xml_name(cx));
xc = NULL; xc = NULL;
while ((xc = xml_child_each(cx, xc, -1)) != NULL) { while ((xc = xml_child_each(cx, xc, CX_ATTR)) != NULL)
if (xml_type(xc) != CX_ATTR)
continue;
clicon_xml2cbuf(cb, xc, level+1, prettyprint); 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, ">"); cprintf(cb, ">");
if (prettyprint && xml_body(cx)==NULL) if (prettyprint && xml_body(cx)==NULL)
cprintf(cb, "\n"); cprintf(cb, "\n");
@ -931,6 +938,7 @@ clicon_xml2cbuf(cbuf *cb,
if (prettyprint && xml_body(cx)==NULL) if (prettyprint && xml_body(cx)==NULL)
cprintf(cb, "%*s", level*XML_INDENT, ""); cprintf(cb, "%*s", level*XML_INDENT, "");
cprintf(cb, "</%s>", xml_name(cx)); cprintf(cb, "</%s>", xml_name(cx));
}
if (prettyprint) if (prettyprint)
cprintf(cb, "\n"); cprintf(cb, "\n");
break; break;
@ -955,11 +963,11 @@ xml_parse(char *str,
return -1; return -1;
} }
ya.ya_xparent = x_up; ya.ya_xparent = x_up;
ya.ya_skipspace = 1; /* remove all non-terminal bodies (strip pretty-print) */
if (clixon_xml_parsel_init(&ya) < 0) if (clixon_xml_parsel_init(&ya) < 0)
goto done; goto done;
if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */ if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */
goto done; goto done;
retval = 0; retval = 0;
done: done:
clixon_xml_parsel_exit(&ya); clixon_xml_parsel_exit(&ya);
@ -1079,7 +1087,6 @@ clicon_xml_parse_file(int fd,
* @endcode * @endcode
* @see clicon_xml_parse_file * @see clicon_xml_parse_file
* @note you need to free the xml parse tree after use, using xml_free() * @note you need to free the xml parse tree after use, using xml_free()
* Update: with yacc parser I dont think it changes,....
*/ */
int int
clicon_xml_parse_str(char *str, clicon_xml_parse_str(char *str,
@ -1090,6 +1097,57 @@ clicon_xml_parse_str(char *str,
return xml_parse(str, *cxtop); 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 /*! Copy single xml node without copying children
*/ */
static int static int
@ -1402,3 +1460,54 @@ xml_body_uint32(cxobj *xb,
cv_free(cv); cv_free(cv);
return 0; 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] x XML Parse-tree (to translate)
* @param[in] prepend0 Print this text in front of all commands. * @param[in] prepend0 Print this text in front of all commands.
* @param[in] gt option to steer cli syntax * @param[in] gt option to steer cli syntax
* @param[in] label Memory chunk allocation label
*/ */
int int
xml2cli(FILE *f, xml2cli(FILE *f,
cxobj *x, cxobj *x,
char *prepend0, char *prepend0,
enum genmodel_type gt, enum genmodel_type gt)
const char *label)
{ {
int retval = -1; int retval = -1;
cxobj *xe = NULL; cxobj *xe = NULL;
char *term; char *term;
char *prepend;
int bool; int bool;
int nr; int nr;
int i; 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); nr = xml_child_nr(x);
if (!nr){ if (!nr){
if (xml_type(x) == CX_BODY) if (xml_type(x) == CX_BODY)
@ -226,10 +229,8 @@ xml2cli(FILE *f,
retval = 0; retval = 0;
goto done; goto done;
} }
prepend = "";
if (prepend0) if (prepend0)
if ((prepend = chunk_sprintf(label, "%s%s", prepend, prepend0)) == NULL) cprintf(cbpre, "%s", prepend0);
goto done;
/* bool determines when to print a variable keyword: /* bool determines when to print a variable keyword:
!leaf T for all (ie parameter) !leaf T for all (ie parameter)
index GT_NONE F index GT_NONE F
@ -241,12 +242,11 @@ xml2cli(FILE *f,
*/ */
bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS && !xml_index(x)); bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS && !xml_index(x));
// bool = (!x->xn_index || gt == GT_ALL); // bool = (!x->xn_index || gt == GT_ALL);
if (bool && if (bool){
(prepend = chunk_sprintf(label, "%s%s%s", if (cbuf_len(cbpre))
prepend, cprintf(cbpre, " ");
strlen(prepend)?" ":"", cprintf(cbpre, "%s", xml_name(x));
xml_name(x))) == NULL) }
goto done;
xe = NULL; xe = NULL;
/* First child is unique, then add that, before looping. */ /* First child is unique, then add that, before looping. */
i = 0; i = 0;
@ -255,23 +255,19 @@ xml2cli(FILE *f,
if (xml_index(xe) && i < nr-1) if (xml_index(xe) && i < nr-1)
; ;
else else
if (xml2cli(f, xe, prepend, gt, label) < 0) if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0)
goto done; goto done;
if (xml_index(xe)){ /* assume index is first, otherwise need one more while */ if (xml_index(xe)){ /* assume index is first, otherwise need one more while */
if (gt ==GT_ALL && (prepend = chunk_sprintf(label, "%s %s", if (gt == GT_ALL)
prepend, cprintf(cbpre, " %s", xml_name(xe));
xml_name(xe))) == NULL) cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0)));
goto done;
if ((prepend = chunk_sprintf(label, "%s %s",
prepend,
xml_value(xml_child_i(xe, 0)))) == NULL)
goto done;
} }
i++; i++;
} }
retval = 0; retval = 0;
done: done:
if (cbpre)
cbuf_free(cbpre);
return retval; return retval;
} }
@ -292,6 +288,7 @@ xml_yang_validate(cxobj *xt,
yang_stmt *yc; yang_stmt *yc;
int i; int i;
yang_stmt *ys; yang_stmt *ys;
char *body;
/* if not given by argument (overide) use default link */ /* if not given by argument (overide) use default link */
ys = ys0?ys0:xml_spec(xt); ys = ys0?ys0:xml_spec(xt);
@ -321,7 +318,8 @@ xml_yang_validate(cxobj *xt,
/* In the union case, value is parsed as generic REST type, /* In the union case, value is parsed as generic REST type,
* needs to be reparsed when concrete type is selected * needs to be reparsed when concrete type is selected
*/ */
if (cv_parse(xml_body(xt), cv) <0){ if ((body = xml_body(xt)) != NULL){
if (cv_parse(body, cv) <0){
clicon_err(OE_UNIX, errno, "cv_parse"); clicon_err(OE_UNIX, errno, "cv_parse");
goto done; goto done;
} }
@ -333,6 +331,7 @@ xml_yang_validate(cxobj *xt,
free(reason); free(reason);
goto done; goto done;
} }
}
break; break;
default: default:
break; break;

View file

@ -46,6 +46,7 @@ struct xml_parse_yacc_arg{
cxobj *ya_xelement; /* xml active element */ cxobj *ya_xelement; /* xml active element */
cxobj *ya_xparent; /* xml parent 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; extern char *clixon_xml_parsetext;

View file

@ -141,11 +141,7 @@ int
clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya) clixon_xml_parsel_exit(struct xml_parse_yacc_arg *ya)
{ {
yy_delete_buffer(ya->ya_lexbuf); yy_delete_buffer(ya->ya_lexbuf);
#if defined(YY_FLEX_SUBMINOR_VERSION) && YY_FLEX_SUBMINOR_VERSION >= 9
clixon_xml_parselex_destroy(); /* modern */ clixon_xml_parselex_destroy(); /* modern */
#else
yy_init = 1; /* This does not quite free all buffers */
#endif
return 0; 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 there may also be some leakage here on NULL return
*/ */
static int 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 *xn = ya->ya_xelement;
cxobj *xp = ya->ya_xparent; cxobj *xp = ya->ya_xparent;
int retval = -1; int retval = -1;
ya->ya_xelement = NULL; /* init */ ya->ya_xelement = NULL; /* init */
s0 = str[0]; if (xn == NULL){
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 = xml_new("body", xp)) == NULL) if ((xn = xml_new("body", xp)) == NULL)
goto done; goto done;
xml_type_set(xn, CX_BODY); xml_type_set(xn, CX_BODY);
sz = 0;
} }
if (xml_value_append(xn, str)==NULL) if (xml_value_append(xn, str)==NULL)
goto done; goto done;
ya->ya_xelement = xn; ya->ya_xelement = xn;
ok:
retval = 0; retval = 0;
done: done:
return retval; return retval;
@ -147,7 +133,6 @@ xml_parse_version(struct xml_parse_yacc_arg *ya, char *ver)
return 0; return 0;
} }
static int static int
xml_parse_id(struct xml_parse_yacc_arg *ya, char *name, char *namespace) xml_parse_id(struct xml_parse_yacc_arg *ya, char *name, char *namespace)
{ {
@ -194,17 +179,30 @@ static int
xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name) xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name)
{ {
int retval = -1; int retval = -1;
cxobj *x = ya->ya_xelement;
cxobj *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", clicon_err(OE_XML, 0, "Sanity check failed: %s vs %s",
xml_name(ya->ya_xelement), name); xml_name(x), name);
goto done; 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", 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; 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; retval = 0;
done: done:
free(name); free(name);
@ -215,24 +213,36 @@ static int
xml_parse_bslash2(struct xml_parse_yacc_arg *ya, char *namespace, char *name) xml_parse_bslash2(struct xml_parse_yacc_arg *ya, char *namespace, char *name)
{ {
int retval = -1; int retval = -1;
cxobj *x = ya->ya_xelement;
cxobj *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", clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n",
xml_namespace(ya->ya_xelement), xml_namespace(x),
xml_name(ya->ya_xelement), xml_name(x),
namespace, namespace,
name); name);
goto done; goto done;
} }
if (xml_namespace(ya->ya_xelement)==NULL || if (xml_namespace(x)==NULL ||
strcmp(xml_namespace(ya->ya_xelement), namespace)){ strcmp(xml_namespace(x), namespace)){
clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n", clicon_err(OE_XML, 0, "Sanity check failed: %s:%s vs %s:%s\n",
xml_namespace(ya->ya_xelement), xml_namespace(x),
xml_name(ya->ya_xelement), xml_name(x),
namespace, namespace,
name); name);
goto done; 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; retval = 0;
done: done:
free(name); free(name);
@ -300,7 +310,7 @@ emnt : '<' id attrs emnt1
; ;
id : NAME { if (xml_parse_id(_YA, $1, NULL) < 0) YYABORT; 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; | NAME ':' NAME { if (xml_parse_id(_YA, $3, $1) < 0) YYABORT;
clicon_debug(3, "id -> NAME : NAME");} clicon_debug(3, "id -> NAME : NAME");}
; ;
@ -314,8 +324,8 @@ emnt1 : ESLASH {_YA->ya_xelement = NULL;
; ;
etg : BSLASH NAME '>' etg : BSLASH NAME '>'
{ if (xml_parse_bslash1(_YA, $2) < 0) YYABORT; { clicon_debug(3, "etg -> < </ NAME %s>", $2); if (xml_parse_bslash1(_YA, $2) < 0) YYABORT; }
clicon_debug(3, "etg -> < </ NAME >"); }
| BSLASH NAME ':' NAME '>' | BSLASH NAME ':' NAME '>'
{ if (xml_parse_bslash2(_YA, $2, $4) < 0) YYABORT; { if (xml_parse_bslash2(_YA, $2, $4) < 0) YYABORT;
clicon_debug(3, "etg -> < </ NAME:NAME >"); } 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"); } content : emnt { clicon_debug(3, "content -> emnt"); }
| comment { clicon_debug(3, "content -> comment"); } | comment { clicon_debug(3, "content -> comment"); }
| CHAR { if (xml_parse_content(_YA, $1) < 0) YYABORT; | 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 -> "); } | { clicon_debug(3, "content -> "); }
; ;

View file

@ -199,7 +199,7 @@ xpath_parse_predicate(struct xpath_element *xe,
int len; int len;
len = strlen(pred); 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]; s = &pred[i];
if (i==0 || if (i==0 ||
(*(s)==']' && *(s+1)=='[')){ (*(s)==']' && *(s+1)=='[')){
@ -358,12 +358,23 @@ xpath_parse(char *xpath,
else if (strncmp(s,"descendant-or-self::", strlen("descendant-or-self::"))==0){ else if (strncmp(s,"descendant-or-self::", strlen("descendant-or-self::"))==0){
xpath_element_new(A_DESCENDANT_OR_SELF, s+strlen("descendant-or-self::"), &xpnext); xpath_element_new(A_DESCENDANT_OR_SELF, s+strlen("descendant-or-self::"), &xpnext);
} }
#if 1 /* 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 */ else if (strncmp(s,".", strlen(s))==0) /* abbreviatedstep */
xpath_element_new(A_SELF, NULL, &xpnext); xpath_element_new(A_SELF, NULL, &xpnext);
#endif
else if (strncmp(s,"self::", strlen("self::"))==0) else if (strncmp(s,"self::", strlen("self::"))==0)
xpath_element_new(A_SELF, s+strlen("self::"), &xpnext); xpath_element_new(A_SELF, s+strlen("self::"), &xpnext);
#if 1
else if (strncmp(s,"..", strlen(".."))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, s+strlen(".."), &xpnext);
#else
else if (strncmp(s,"..", strlen(s))==0) /* abbreviatedstep */ else if (strncmp(s,"..", strlen(s))==0) /* abbreviatedstep */
xpath_element_new(A_PARENT, NULL, &xpnext); xpath_element_new(A_PARENT, NULL, &xpnext);
#endif
else if (strncmp(s,"parent::", strlen("parent::"))==0) else if (strncmp(s,"parent::", strlen("parent::"))==0)
xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext); xpath_element_new(A_PARENT, s+strlen("parent::"), &xpnext);
else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0) else if (strncmp(s,"ancestor::", strlen("ancestor::"))==0)
@ -552,12 +563,6 @@ xpath_expr(char *predicate_expression,
* @param[in] flags if != 0, only match xml nodes matching flags * @param[in] flags if != 0, only match xml nodes matching flags
* @param[out] vec2 Result XML node vector * @param[out] vec2 Result XML node vector
* @param[out] vec2len Length of result 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 static int
xpath_find(struct xpath_element *xe, xpath_find(struct xpath_element *xe,
@ -767,6 +772,7 @@ xpath_choice(cxobj *xtop,
cxobj **vec0 = NULL; cxobj **vec0 = NULL;
size_t vec0len = 0; size_t vec0len = 0;
if ((s0 = strdup(xpath0)) == NULL){ if ((s0 = strdup(xpath0)) == NULL){
clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__); clicon_err(OE_XML, errno, "%s: strdup", __FUNCTION__);
goto done; goto done;
@ -926,8 +932,8 @@ xpath_each(cxobj *cxtop,
/*! A restricted xpath that returns a vector of matches /*! A restricted xpath that returns a vector of matches
* *
* See xpath1() on details for subset. * See xpath1() on details for subset
* @param[in] cxtop xml-tree where to search . * @param[in] cxtop xml-tree where to search
* @param[in] xpath string with XPATH syntax * @param[in] xpath string with XPATH syntax
* @param[out] vec vector of xml-trees. Vector must be free():d after use * @param[out] vec vector of xml-trees. Vector must be free():d after use
* @param[out] veclen returns length of vector in return value * @param[out] veclen returns length of vector in return value
@ -945,7 +951,7 @@ xpath_each(cxobj *cxtop,
* } * }
* free(vec); * free(vec);
* @endcode * @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. * trees need not be.
* @see also xpath_first, xpath_each. * @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) /* 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[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 int
xpath_vec_flag(cxobj *cxtop, xpath_vec_flag(cxobj *cxtop,

View file

@ -1676,37 +1676,6 @@ yang_xpath_vec(yang_node *yn,
return yret; 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 /*! Given an absolute xpath (eg /a/b/c) find matching yang specification
* @param[in] yn Yang node * @param[in] yn Yang node
@ -1725,12 +1694,10 @@ yang_xpath_abs(yang_node *yn,
char *id; char *id;
char *prefix = NULL; char *prefix = NULL;
if ((vec = clicon_strsplit_malloc(xpath, "/", &nvec)) == NULL){ if ((vec = clicon_strsep(xpath, "/", &nvec)) == NULL){
clicon_err(OE_YANG, errno, "%s: strsplit", __FUNCTION__); clicon_err(OE_YANG, errno, "%s: strsep", __FUNCTION__);
return NULL; return NULL;
} }
/* Assume path looks like: "/prefix:id[/prefix:id]*" */ /* Assume path looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){ if (nvec < 2){
clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s", clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s",
@ -1807,7 +1774,7 @@ yang_xpath(yang_node *yn,
/* check absolute path */ /* check absolute path */
if (xpath[0] == '/') if (xpath[0] == '/')
return yang_xpath_abs(yn, xpath); return yang_xpath_abs(yn, xpath);
if ((vec = clicon_strsplit_malloc(xpath, "/", &nvec)) == NULL) if ((vec = clicon_strsep(xpath, "/", &nvec)) == NULL)
goto err; goto err;
ys = yang_xpath_vec((yang_node*)yn, vec, nvec); ys = yang_xpath_vec((yang_node*)yn, vec, nvec);
err: err:
@ -1986,16 +1953,14 @@ cvec *
yang_arg2cvec(yang_stmt *ys, yang_arg2cvec(yang_stmt *ys,
char *delim) char *delim)
{ {
char **vec; char **vec = NULL;
int i; int i;
int nvec; int nvec;
cvec *cvv = NULL; cvec *cvv = NULL;
cg_var *cv; cg_var *cv;
if ((vec = clicon_strsplit(ys->ys_argument, " ", &nvec, __FUNCTION__)) == NULL){ if ((vec = clicon_strsep(ys->ys_argument, " ", &nvec)) == NULL)
clicon_err(OE_YANG, errno, "clicon_strsplit");
goto done; goto done;
}
if ((cvv = cvec_new(nvec)) == NULL){ if ((cvv = cvec_new(nvec)) == NULL){
clicon_err(OE_YANG, errno, "cvec_new"); clicon_err(OE_YANG, errno, "cvec_new");
goto done; goto done;
@ -2010,7 +1975,8 @@ yang_arg2cvec(yang_stmt *ys,
} }
} }
done: done:
unchunk_group(__FUNCTION__); if (vec)
free(vec);
return cvv; return cvv;
} }

View file

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

View file

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

View file

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
testnr=0 testnr=0
testnname= testnname=
@ -32,7 +32,7 @@ expectfn(){
fi fi
# grep extended grep # grep extended grep
match=`echo "$ret" | grep -Eo "$expect"` match=`echo "$ret" | grep -Eo "$expect"`
# echo "ret:$ret" # echo "ret:<$ret>"
# echo "expect:$expect" # echo "expect:$expect"
# echo "match:$match" # echo "match:$match"
if [ -z "$match" ]; then if [ -z "$match" ]; then
@ -61,3 +61,21 @@ EOF
fi 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 # Test1: backend and cli basic functionality
# Start backend server # Start backend server
# Add an ethernet interface and an address # Add an ethernet interface and an address
@ -10,6 +10,10 @@
# include err() and new() functions # include err() and new() functions
. ./lib.sh . ./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) # kill old backend (if any)
new "kill old backend" new "kill old backend"
sudo clixon_backend -zf $clixon_cf sudo clixon_backend -zf $clixon_cf
@ -18,26 +22,72 @@ if [ $? -ne 0 ]; then
fi fi
new "start backend" new "start backend"
# start new 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 if [ $? -ne 0 ]; then
err err
fi 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" 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" 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$" interfaces interface enabled true$"
new "cli failed validate" 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" 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 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 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" 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" new "Kill backend"
# Check if still alive # Check if still alive

View file

@ -1,15 +1,13 @@
#!/bin/sh #!/bin/bash
# Test1: backend and cli basic functionality # Test2: backend and netconf basic functionality
# Start backend server
# Add an ethernet interface and an address
# Show configuration
# Validate without a mandatory type
# Set the mandatory type
# Commit
# include err() and new() functions # include err() and new() functions
. ./lib.sh . ./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) # kill old backend (if any)
new "kill old backend" new "kill old backend"
sudo clixon_backend -zf $clixon_cf sudo clixon_backend -zf $clixon_cf
@ -18,15 +16,87 @@ if [ $? -ne 0 ]; then
fi fi
new "start backend" new "start backend"
# start new 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 if [ $? -ne 0 ]; then
err err
fi fi
new "netconf show config" new "netconf get empty config"
expecteof "clixon_netconf -qf $clixon_cf" "<rpc><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "<rpc-reply><data></data></rpc-reply>]]>]]>" expecteof "$clixon_netconf -qf $clixon_cf" "<rpc message-id=\"101\"><get-config><source><candidate/></source></get-config></rpc>]]>]]>" "^<rpc-reply message-id=\"101\"><data/></rpc-reply>]]>]]>$"
new "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" new "netconf lock"
expecteof "clixon_netconf -qf $clixon_cf" "<rpc><lock><target><candidate/></target></lock></rpc>]]>]]>" "<rpc-reply><ok/></rpc-reply>]]>]]>" 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" new "Kill backend"
# Check if still alive # Check if still alive
@ -39,4 +109,3 @@ sudo clixon_backend -zf $clixon_cf
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err "kill backend" err "kill backend"
fi 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