internal netconf mods

This commit is contained in:
Olof hagsand 2017-03-25 18:24:52 +01:00
parent 2fcefda831
commit eec5896797
22 changed files with 665 additions and 1205 deletions

View file

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

View file

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

View file

@ -71,31 +71,59 @@
#include "clixon_xml.h"
#include "clixon_xsl.h"
#include "clixon_proto.h"
#include "clixon_proto_encode.h"
static int _atomicio_sig = 0;
struct map_type2str{
enum clicon_msg_type mt_type;
char *mt_str; /* string as in 4.2.4 in RFC 6020 */
};
/* Mapping between yang keyword string <--> clicon constants */
static const struct map_type2str msgmap[] = {
{CLICON_MSG_NETCONF, "netconf"},
{CLICON_MSG_CHANGE, "change"},
{-1, NULL},
};
static char *
msg_type2str(enum clicon_msg_type type)
/*! Encode a clicon netconf message
* @param[in] param 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, ...)
{
const struct map_type2str *mt;
va_list args;
int xmllen;
int len;
struct clicon_msg *msg = NULL;
int hdrlen = sizeof(*msg);
for (mt = &msgmap[0]; mt->mt_str; mt++)
if (mt->mt_type == type)
return mt->mt_str;
return NULL;
va_start(args, format);
xmllen = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
len = hdrlen + xmllen;
if ((msg = (struct clicon_msg *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_len = htons(len);
/* body */
va_start(args, format);
vsnprintf(msg->op_body, xmllen, format, args);
va_end(args);
return msg;
}
/*! Decode a clicon netconf message
*/
int
clicon_msg_decode(struct clicon_msg *msg,
cxobj **xml)
{
int retval = -1;
char *xmlstr;
/* body */
xmlstr = msg->op_body;
if (clicon_xml_parse_str(xmlstr, xml) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Open local connection using unix domain sockets
@ -198,8 +226,8 @@ clicon_msg_send(int s,
{
int retval = -1;
clicon_debug(2, "%s: send msg seq=%d len=%d",
__FUNCTION__, ntohs(msg->op_type), ntohs(msg->op_len));
clicon_debug(2, "%s: send msg len=%d",
__FUNCTION__, ntohs(msg->op_len));
if (debug > 2)
msg_dump(msg);
if (atomicio((ssize_t (*)(int, void *, size_t))write,
@ -258,8 +286,8 @@ clicon_msg_rcv(int s,
goto done;
}
mlen = ntohs(hdr.op_len);
clicon_debug(2, "%s: rcv msg seq=%d, len=%d",
__FUNCTION__, ntohs(hdr.op_type), mlen);
clicon_debug(2, "%s: rcv msg len=%d",
__FUNCTION__, mlen);
if ((*msg = (struct clicon_msg *)malloc(mlen)) == NULL){
clicon_err(OE_CFG, errno, "malloc");
goto done;
@ -303,8 +331,7 @@ clicon_rpc_connect_unix(struct clicon_msg *msg,
int s = -1;
struct stat sb;
clicon_debug(1, "Send %s msg on %s",
msg_type2str(ntohs(msg->op_type)), sockpath);
clicon_debug(1, "Send msg on %s", sockpath);
/* special error handling to get understandable messages (otherwise ENOENT) */
if (stat(sockpath, &sb) < 0){
clicon_err(OE_PROTO, errno, "%s: config daemon not running?", sockpath);
@ -349,8 +376,7 @@ clicon_rpc_connect_inet(struct clicon_msg *msg,
int s = -1;
struct sockaddr_in addr;
clicon_debug(1, "Send %s msg to %s:%hu",
msg_type2str(ntohs(msg->op_type)), dst, port);
clicon_debug(1, "Send msg to %s:%hu", dst, port);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
@ -400,7 +426,6 @@ clicon_rpc(int s,
int retval = -1;
struct clicon_msg *reply;
int eof;
enum clicon_msg_type type;
char *data = NULL;
cxobj *cx = NULL;
@ -414,17 +439,7 @@ clicon_rpc(int s,
errno = ESHUTDOWN;
goto done;
}
type = ntohs(reply->op_type);
switch (type){
case CLICON_MSG_NETCONF: /* ok or rpc-error expected */
data = reply->op_body; /* assume string */
break;
default:
clicon_err(OE_PROTO, 0, "%s: unexpected reply: %s",
__FUNCTION__, msg_type2str(type));
goto done;
break;
}
data = reply->op_body; /* assume string */
if (ret && data)
if ((*ret = strdup(data)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
@ -442,7 +457,6 @@ clicon_rpc(int s,
/*! Send a clicon_msg message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client
* @param[in] type Clicon msg operation, see enum clicon_msg_type
* @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
@ -450,7 +464,6 @@ clicon_rpc(int s,
*/
int
send_msg_reply(int s,
uint16_t type,
char *data,
uint16_t datalen)
{
@ -462,7 +475,6 @@ send_msg_reply(int s,
if ((reply = (struct clicon_msg *)chunk(len, __FUNCTION__)) == NULL)
goto done;
memset(reply, 0, len);
reply->op_type = htons(type);
reply->op_len = htons(len);
if (datalen > 0)
memcpy(reply->op_body, data, datalen);
@ -490,7 +502,7 @@ send_msg_notify(int s,
int retval = -1;
struct clicon_msg *msg = NULL;
if ((msg=clicon_msg_netconf_encode("<notification><event>%s</event></notification>", event)) == NULL)
if ((msg=clicon_msg_encode("<notification><event>%s</event></notification>", event)) == NULL)
goto done;
if (clicon_msg_send(s, msg) < 0)
goto done;
@ -500,134 +512,3 @@ send_msg_notify(int s,
free(msg);
return retval;
}
/*! Send a clicon_msg OK message as reply to a clicon rpc request
*
* @param[in] s Socket to communicate with client
* @param[in] data Returned data as byte-string.
* @retval 0 OK
* @retval -1 Error
* @note send as netconf message XXX remove clicon header
*/
int
send_msg_ok(int s,
char *data)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL)
goto done;
if (data)
cprintf(cb, "<rpc-reply><ok>%s</ok></rpc-reply>", data);
else
cprintf(cb, "<rpc-reply><ok/></rpc-reply>");
if (send_msg_reply(s, CLICON_MSG_NETCONF, cbuf_get(cb), cbuf_len(cb)+1) < 0){
if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset");
goto done;
}
retval=0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Send a clicon_msg Error 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
* @retval 0 OK
* @retval -1 Error
* @note send as netconf message XXX remove clicon header
*/
int
send_msg_err(int s,
int err,
int suberr,
char *format, ...)
{
va_list args;
char *info = NULL;
int len;
int retval = -1;
cbuf *cb = NULL;
va_start(args, format);
len = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
if ((info = malloc(len)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(info, 0, len);
va_start(args, format);
vsnprintf(info, len, format, args);
va_end(args);
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "<rpc-reply><rpc-error>"
"<error-type>clixon</error-type>"
"<error-tag>%d</error-tag>"
"<error-message>%d</error-message>"
"<error-info>%s</error-info>"
"</rpc-error></rpc-reply>",
err, suberr, info);
if (send_msg_reply(s, CLICON_MSG_NETCONF, cbuf_get(cb), cbuf_len(cb)+1) < 0){
if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
if (info)
free(info);
return retval;
}
/*! Send a clicon_msg Error 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
* @retval 0 OK
* @retval -1 Error
* @note send as netconf message XXX remove clicon header
* XXX: see clicon_xml_parse
*/
int
send_msg_netconf_reply(int s,
char *format, ...)
{
va_list args;
char *str = NULL;
int len;
int retval = -1;
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);
va_end(args);
if (send_msg_reply(s, CLICON_MSG_NETCONF, str, len) < 0){
if (errno == ECONNRESET)
clicon_log(LOG_WARNING, "client rpc reset");
goto done;
}
retval = 0;
done:
if (str)
free(str);
return retval;
}

View file

@ -66,10 +66,9 @@
#include "clixon_xsl.h"
#include "clixon_proto.h"
#include "clixon_err.h"
#include "clixon_proto_encode.h"
#include "clixon_proto_client.h"
/*! Internal rpc function
/*! Send internal netconf rpc from client to backend
* @param[in] h CLICON handle
* @param[in] msg Encoded message. Deallocate woth free
* @param[out] xret Return value from backend as netconf xml tree. Free w xml_free
@ -88,11 +87,6 @@ clicon_rpc_msg(clicon_handle h,
int port;
char *retdata = NULL;
cxobj *xret = NULL;
cxobj *x;
uint32_t err;
uint32_t suberr;
char *errmsg = NULL;
char *etype = NULL;
if ((sock = clicon_sock(h)) == NULL){
clicon_err(OE_FATAL, 0, "CLICON_SOCK option not set");
@ -124,38 +118,9 @@ clicon_rpc_msg(clicon_handle h,
goto done;
break;
}
#if 1 /* Parse return data and intercept clixon errors */
if (retdata) {
if (clicon_xml_parse_str(retdata, &xret) < 0)
goto done;
if (xpath_first(xret, "/rpc-reply/ok") != NULL ||
xpath_first(xret, "/rpc-reply/data") != NULL)
;
else if (xpath_first(xret, "/rpc-reply/rpc-error") != NULL){
if ((x=xpath_first(xret, "//error-type"))!=NULL)
etype = xml_body(x);
if (etype&&strcmp(etype, "clixon")==0){
if ((x=xpath_first(xret, "//error-tag"))!=NULL)
err = atoi(xml_body(x));
if ((x=xpath_first(xret, "//error-message"))!=NULL)
suberr = atoi(xml_body(x));
if ((x=xpath_first(xret, "//error-info"))!=NULL)
errmsg = xml_body(x);
if (debug)
clicon_err(err, suberr, "%s msgtype:%hu", errmsg, ntohs(msg->op_type));
else
clicon_err(err, suberr, "%s", errmsg);
goto done;
}
}
else{
clicon_err(OE_PROTO, 0, "%s: unexpected reply",
__FUNCTION__);
}
}
#endif
if (retdata &&
clicon_xml_parse_str(retdata, &xret) < 0)
goto done;
if (xret0){
*xret0 = xret;
xret = NULL;
@ -172,20 +137,21 @@ clicon_rpc_msg(clicon_handle h,
/*! Generic xml netconf clicon rpc
* Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle
* @param[in] xi XML netconf tree
* @param[out] xret Return XML, error or OK
* @param[in] xmlstr XML netconf tree as string
* @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL
* @see clicon_rpc_netconf_xml xml as tree instead of string
*/
int
clicon_rpc_netconf_xml(clicon_handle h,
cxobj *xin,
cxobj **xret,
int *sp)
clicon_rpc_netconf(clicon_handle h,
char *xmlstr,
cxobj **xret,
int *sp)
{
int retval = -1;
struct clicon_msg *msg = NULL;
if ((msg = clicon_msg_netconf_encode_xml(xin)) == NULL)
if ((msg = clicon_msg_encode("%s", xmlstr)) < 0)
goto done;
if (clicon_rpc_msg(h, msg, xret, sp) < 0)
goto done;
@ -196,25 +162,64 @@ clicon_rpc_netconf_xml(clicon_handle h,
return retval;
}
/*! Generic xml netconf clicon rpc
* Want to go over to use netconf directly between client and server,...
* @param[in] h clicon handle
* @param[in] xml XML netconf tree
* @param[out] xret Return XML netconf tree, error or OK
* @param[out] sp Socket pointer for notification, otherwise NULL
* @see clicon_rpc_netconf xml as string instead of tree
*/
int
clicon_rpc_netconf_xml(clicon_handle h,
cxobj *xml,
cxobj **xret,
int *sp)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xml, 0, 0) < 0)
goto done;
if (clicon_rpc_netconf(h, cbuf_get(cb), xret, sp) < 0)
goto done;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Generate clicon error function call from Netconf error message
* @param[in] xerr Netconf error message on the level: <rpc-reply><rpc-error>
*/
int
clicon_rpc_generate_error(cxobj *xerr)
{
int retval = -1;
cbuf *cb = NULL;
cxobj *x;
char *etype="";
char *etag="";
char *emsg="";
char *einfo="";
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if ((x=xpath_first(xerr, "error-type"))!=NULL)
etype = xml_body(x);
cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-tag"))!=NULL)
etag = xml_body(x);
cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-message"))!=NULL)
emsg = xml_body(x);
cprintf(cb, "%s ", xml_body(x));
if ((x=xpath_first(xerr, "error-info"))!=NULL)
einfo = xml_body(x);
clicon_err(OE_XML, 0, "%s %s %s %s", etype, etag, emsg, einfo);
return 0;
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:
return retval;
}
/*! Get database configuration
@ -252,7 +257,7 @@ clicon_rpc_get_config(clicon_handle h,
if (xpath && strlen(xpath))
cprintf(cb, "<filter type=\"xpath\" select=\"%s\"/>", xpath);
cprintf(cb, "</get-config></rpc>");
if ((msg = clicon_msg_netconf_encode(cbuf_get(cb))) == NULL)
if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -280,14 +285,14 @@ clicon_rpc_get_config(clicon_handle h,
}
/*! Send database entries as XML to backend daemon
* Same as clicon_proto_change just with a cvec instead of lvec
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: OP_MERGE, OP_REPLACE
* @param[in] api_path restconf API Path (or "")
* @param[in] xml XML string. Ex: <a>..</a><b>...</b>
* @param[in] xml XML string. Ex: <config><a>..</a><b>...</b></config>
* @retval 0 OK
* @retval -1 Error
* @note xml arg need to have <config> as top element
* @code
* if (clicon_rpc_edit_config(h, "running", OP_MERGE, "/",
* "<config><a>4</a></config>") < 0)
@ -299,7 +304,7 @@ clicon_rpc_edit_config(clicon_handle h,
char *db,
enum operation_type op,
char *api_path,
char *xml)
char *xmlstr)
{
int retval = -1;
struct clicon_msg *msg = NULL;
@ -314,10 +319,10 @@ clicon_rpc_edit_config(clicon_handle h,
xml_operation2str(op));
if (api_path && strlen(api_path))
cprintf(cb, "<filter type=\"restconf\" select=\"%s\"/>", api_path);
if (xml)
cprintf(cb, "%s", xml);
if (xmlstr)
cprintf(cb, "%s", xmlstr);
cprintf(cb, "</edit-config></rpc>");
if ((msg = clicon_msg_netconf_encode(cbuf_get(cb))) == NULL)
if ((msg = clicon_msg_encode(cbuf_get(cb))) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -357,7 +362,7 @@ clicon_rpc_copy_config(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><copy-config><source><%s/></source><target><%s/></target></copy-config></rpc>", db1, db2)) == NULL)
if ((msg = clicon_msg_encode("<rpc><copy-config><source><%s/></source><target><%s/></target></copy-config></rpc>", db1, db2)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -391,7 +396,7 @@ clicon_rpc_delete_config(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><delete-config><target><%s/></target></delete-config></rpc>", db)) == NULL)
if ((msg = clicon_msg_encode("<rpc><delete-config><target><%s/></target></delete-config></rpc>", db)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -408,6 +413,10 @@ clicon_rpc_delete_config(clicon_handle h,
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)
@ -417,7 +426,7 @@ clicon_rpc_lock(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><lock><target><%s/></target></lock></rpc>", db)) == NULL)
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;
@ -434,6 +443,10 @@ clicon_rpc_lock(clicon_handle h,
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)
@ -443,7 +456,7 @@ clicon_rpc_unlock(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><unlock><target><%s/></target></unlock></rpc>", db)) == NULL)
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;
@ -460,6 +473,9 @@ clicon_rpc_unlock(clicon_handle h,
return retval;
}
/*! Close a (user) session
* @param[in] h CLICON handle
*/
int
clicon_rpc_close_session(clicon_handle h)
{
@ -468,7 +484,7 @@ clicon_rpc_close_session(clicon_handle h)
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><close-session/></rpc>")) == NULL)
if ((msg = clicon_msg_encode("<rpc><close-session/></rpc>")) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -485,6 +501,10 @@ clicon_rpc_close_session(clicon_handle h)
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)
@ -494,7 +514,7 @@ clicon_rpc_kill_session(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><kill-session><session-id>%d</session-id></kill-session></rpc>", session_id)) == NULL)
if ((msg = clicon_msg_encode("<rpc><kill-session><session-id>%d</session-id></kill-session></rpc>", session_id)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
@ -511,7 +531,6 @@ clicon_rpc_kill_session(clicon_handle h,
return retval;
}
/*! Send validate request to backend daemon
* @param[in] h CLICON handle
* @param[in] db Name of database
@ -527,7 +546,7 @@ clicon_rpc_validate(clicon_handle h,
cxobj *xerr;
cxobj *x;
if ((msg = clicon_msg_netconf_encode("<rpc><validate><source><%s/></source></validate></rpc>", db)) == NULL)
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;
@ -547,8 +566,6 @@ clicon_rpc_validate(clicon_handle h,
/*! Commit changes send a commit request to backend daemon
* @param[in] h CLICON handle
* @param[in] from name of 'from' database (eg "candidate")
* @param[in] db name of 'to' database (eg "running")
* @retval 0 OK
*/
int
@ -559,7 +576,36 @@ clicon_rpc_commit(clicon_handle h)
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><commit/></rpc>")) == NULL)
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;
@ -594,11 +640,11 @@ clicon_rpc_create_subscription(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><create-subscription>"
"<stream>%s</stream>"
"<filter>%s</filter>"
"</create-subscription></rpc>",
stream?stream:"", filter?filter:"")) == NULL)
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;
@ -615,43 +661,6 @@ clicon_rpc_create_subscription(clicon_handle h,
return retval;
}
/*! Send database change request to backend daemon, variant for xmldb
* Same as clicon_proto_change just with a string
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] op Operation on database item: set, delete, (merge?)
* @param[in] key Database key
* @param[in] value value as string
* @retval 0 OK
* @retval -1 Error
* @note special case: remove all: key:"/" op:OP_REMOVE
*/
int
clicon_rpc_change(clicon_handle h,
char *db,
enum operation_type op,
char *key,
char *val)
{
int retval = -1;
struct clicon_msg *msg = NULL;
if ((msg = clicon_msg_change_encode(db,
op,
key,
val,
val?strlen(val)+1:0)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, NULL, NULL) < 0)
goto done;
retval = 0;
done:
if (msg)
free(msg);
return retval;
}
/*! Send a debug request to backend server
* @param[in] h CLICON handle
* @param[in] level Debug level
@ -665,7 +674,7 @@ clicon_rpc_debug(clicon_handle h,
cxobj *xret = NULL;
cxobj *xerr;
if ((msg = clicon_msg_netconf_encode("<rpc><debug><level>%d</level></debug></rpc>", level)) == NULL)
if ((msg = clicon_msg_encode("<rpc><debug><level>%d</level></debug></rpc>", level)) == NULL)
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;

View file

@ -1,244 +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_xml.h"
#include "clixon_proto.h"
#include "clixon_proto_encode.h"
struct clicon_msg *
clicon_msg_netconf_encode(char *format, ...)
{
va_list args;
int xmllen;
int len;
struct clicon_msg *msg = NULL;
int hdrlen = sizeof(*msg);
va_start(args, format);
xmllen = vsnprintf(NULL, 0, format, args) + 1;
va_end(args);
len = hdrlen + xmllen;
if ((msg = (struct clicon_msg *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
return NULL;
}
memset(msg, 0, len);
/* hdr */
msg->op_type = htons(CLICON_MSG_NETCONF);
msg->op_len = htons(len);
/* body */
va_start(args, format);
vsnprintf(msg->op_body, xmllen, format, args);
va_end(args);
return msg;
}
struct clicon_msg *
clicon_msg_netconf_encode_xml(cxobj *xml)
{
struct clicon_msg *msg = NULL;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_XML, errno, "cbuf_new");
goto done;
}
if (clicon_xml2cbuf(cb, xml, 0, 0) < 0)
goto done;
if ((msg = clicon_msg_netconf_encode("%s", cbuf_get(cb))) < 0)
goto done;
done:
if (cb)
cbuf_free(cb);
return msg;
}
int
clicon_msg_netconf_decode(struct clicon_msg *msg,
cxobj **xml)
{
int retval = -1;
char *xmlstr;
/* body */
xmlstr = msg->op_body;
if (clicon_xml_parse_str(xmlstr, xml) < 0)
goto done;
retval = 0;
done:
return retval;
}
struct clicon_msg *
clicon_msg_change_encode(char *db,
uint32_t op,
char *key,
char *str,
uint32_t str_len)
{
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 *)malloc(len)) == NULL){
clicon_err(OE_PROTO, errno, "malloc");
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;
}

View file

@ -1110,7 +1110,7 @@ clicon_xml_parse_str(char *str,
*
* @code
* cxobj *cx = NULL;
* if (clicon_xml_parse(&cx, "<xml>%s</xml", 22) < 0)
* if (clicon_xml_parse(&cx, "<xml>%d</xml>", 22) < 0)
* err;
* xml_free(cx);
* @endcode

View file

@ -230,6 +230,7 @@ yang2xmlkeyfmt(yang_stmt *ys,
return retval;
}
/*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example:
@ -1183,6 +1184,225 @@ put(char *dbname,
return retval;
}
/*! Modify database provided an XML database key and an operation
* @param[in] h CLICON handle
* @param[in] db Database name
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] xk XML Key, eg /aa/bb=17/name
* @param[in] val Key value, eg "17"
* @retval 0 OK
* @retval -1 Error
* @code
* if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
* err;
* @endcode
* @see xmldb_put with xml-tree, no path
*/
static int
xmldb_put_xkey(clicon_handle h,
char *db,
enum operation_type op,
char *xk,
char *val)
{
int retval = -1;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec;
int nvec;
char **valvec;
int nvalvec;
int i;
int j;
char *name;
char *restval;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2 = NULL;
cbuf *ckey=NULL; /* partial keys */
cbuf *csubkey=NULL; /* partial keys */
cbuf *crx=NULL; /* partial keys */
char *keyname;
int exists;
int npairs;
struct db_pair *pairs;
yang_spec *yspec;
char *filename = NULL;
yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &filename) < 0)
goto done;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", xk);
goto done;
}
if ((ckey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((csubkey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((vec = clicon_strsplit(xk, "/", &nvec, __FUNCTION__)) == NULL)
goto done;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (i==1){
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/");
break;
}
else
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", x?xml_name(x):"");
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;
}
if ((op==OP_DELETE || op == OP_REMOVE) &&
y->ys_keyword == Y_LEAF &&
y->ys_parent->yn_keyword == Y_LIST &&
yang_key_match(y->ys_parent, y->ys_argument))
/* Special rule if key, dont write last key-name, rm whole*/;
else
cprintf(ckey, "/%s", name);
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
cprintf(ckey, "=%s", restval);
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if ((valvec = clicon_strsplit(restval, ",", &nvalvec, __FUNCTION__)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Iterate over individual yang keys */
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (j)
cprintf(ckey, ",");
else
cprintf(ckey, "=");
val2 = valvec[j++];
cprintf(ckey, "%s", val2);
cbuf_reset(csubkey);
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default:
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
goto done;
break;
}
}
xk = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
goto done;
}
else
if (db_set(filename, xk, NULL, 0) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk);
goto done;
}
case OP_REMOVE:
/* Read in complete database (this can be optimized) */
if ((crx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(crx, "^%s.*$", xk);
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
goto done;
for (i = 0; i < npairs; i++) {
if (db_del(filename, pairs[i].dp_key) < 0)
goto done;
}
break;
default:
break;
}
retval = 0;
done:
if (filename)
free(filename);
if (ckey)
cbuf_free(ckey);
if (csubkey)
cbuf_free(csubkey);
if (crx)
cbuf_free(crx);
if (cvk)
cvec_free(cvk);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Modify database provided an xml tree, a restconf api_path and an operation
*
* @param[in] h CLICON handle
@ -1430,6 +1650,8 @@ xmldb_put(clicon_handle h,
yang_spec *yspec;
char *dbfilename = NULL;
if (xml_child_nr(xt)==0 || xml_body(xt)!= NULL)
return xmldb_put_xkey(h, db, op, api_path, xml_body(xt));
yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &dbfilename) < 0)
goto done;
@ -1464,224 +1686,6 @@ xmldb_put(clicon_handle h,
return retval;
}
/*! Modify database provided an XML database key and an operation
* @param[in] h CLICON handle
* @param[in] db Database name
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] xk XML Key, eg /aa/bb=17/name
* @param[in] val Key value, eg "17"
* @retval 0 OK
* @retval -1 Error
* @code
* if (xmldb_put_xkey(h, db, OP_MERGE, "/aa/bb=17/name", "17") < 0)
* err;
* @endcode
* @see xmldb_put with xml-tree, no path
*/
int
xmldb_put_xkey(clicon_handle h,
char *db,
enum operation_type op,
char *xk,
char *val)
{
int retval = -1;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec;
int nvec;
char **valvec;
int nvalvec;
int i;
int j;
char *name;
char *restval;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2 = NULL;
cbuf *ckey=NULL; /* partial keys */
cbuf *csubkey=NULL; /* partial keys */
cbuf *crx=NULL; /* partial keys */
char *keyname;
int exists;
int npairs;
struct db_pair *pairs;
yang_spec *yspec;
char *filename = NULL;
yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &filename) < 0)
goto done;
if (xk == NULL || *xk!='/'){
clicon_err(OE_DB, 0, "Invalid key: %s", xk);
goto done;
}
if ((ckey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((csubkey = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if ((vec = clicon_strsplit(xk, "/", &nvec, __FUNCTION__)) == NULL)
goto done;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", xk);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i]; /* E.g "x=1,2" -> name:x restval=1,2 */
if ((restval = index(name, '=')) != NULL){
*restval = '\0';
restval++;
}
if (i==1){
if (strlen(name)==0 && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/");
break;
}
else
if ((y = yang_find_topnode(yspec, name)) == NULL){
clicon_err(OE_UNIX, errno, "No yang node found: %s", x?xml_name(x):"");
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;
}
if ((op==OP_DELETE || op == OP_REMOVE) &&
y->ys_keyword == Y_LEAF &&
y->ys_parent->yn_keyword == Y_LIST &&
yang_key_match(y->ys_parent, y->ys_argument))
/* Special rule if key, dont write last key-name, rm whole*/;
else
cprintf(ckey, "/%s", name);
i++;
switch (y->ys_keyword){
case Y_LEAF_LIST:
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
cprintf(ckey, "=%s", restval);
break;
case Y_LIST:
if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){
clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key",
__FUNCTION__, y->ys_argument);
goto done;
}
/* The value is a list of keys: <key>[ <key>]* */
if ((cvk = yang_arg2cvec(ykey, " ")) == NULL)
goto done;
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=<restval>'");
goto done;
}
if ((valvec = clicon_strsplit(restval, ",", &nvalvec, __FUNCTION__)) == NULL)
goto done;
if (cvec_len(cvk) != nvalvec){
clicon_err(OE_XML, errno, "List %s key length mismatch", name);
goto done;
}
cvi = NULL;
/* Iterate over individual yang keys */
j = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if (j)
cprintf(ckey, ",");
else
cprintf(ckey, "=");
val2 = valvec[j++];
cprintf(ckey, "%s", val2);
cbuf_reset(csubkey);
cprintf(csubkey, "%s/%s", cbuf_get(ckey), keyname);
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(csubkey), val2, strlen(val2)+1) < 0)
goto done;
}
if (cvk){
cvec_free(cvk);
cvk = NULL;
}
break;
default:
if (op == OP_MERGE || op == OP_REPLACE || op == OP_CREATE)
if (db_set(filename, cbuf_get(ckey), NULL, 0) < 0)
goto done;
break;
}
}
xk = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (y->ys_keyword == Y_LEAF || y->ys_keyword == Y_LEAF_LIST){
if (db_set(filename, xk, val, val?strlen(val)+1:0) < 0)
goto done;
}
else
if (db_set(filename, xk, NULL, 0) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, xk)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk);
goto done;
}
case OP_REMOVE:
/* Read in complete database (this can be optimized) */
if ((crx = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
cprintf(crx, "^%s.*$", xk);
if ((npairs = db_regexp(filename, cbuf_get(crx), __FUNCTION__, &pairs, 0)) < 0)
goto done;
for (i = 0; i < npairs; i++) {
if (db_del(filename, pairs[i].dp_key) < 0)
goto done;
}
break;
default:
break;
}
retval = 0;
done:
if (filename)
free(filename);
if (ckey)
cbuf_free(ckey);
if (csubkey)
cbuf_free(csubkey);
if (crx)
cbuf_free(crx);
if (cvk)
cvec_free(cvk);
unchunk_group(__FUNCTION__);
return retval;
}
/*! Raw dump of database, just keys and values, no xml interpretation
* @param[in] f File