restconf; xmlput extended with api_path; xmldb_put_tree

This commit is contained in:
Olof hagsand 2016-09-20 21:51:08 +02:00
parent ebd53a34ee
commit b744a4ad8a
21 changed files with 732 additions and 1260 deletions

View file

@ -246,13 +246,16 @@ from_client_xmlput(clicon_handle h,
enum operation_type op;
cvec *cvv = NULL;
char *str = NULL;
char *api_path = NULL;
char *xml = NULL;
cxobj *xt = NULL;
int piddb;
cxobj *x;
if (clicon_msg_xmlput_decode(msg,
&db,
&op,
&api_path,
&xml,
label) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
@ -273,7 +276,25 @@ from_client_xmlput(clicon_handle h,
clicon_err_reason);
goto done;
}
if (xmldb_put(h, db, xt, op) < 0){
if (strlen(api_path)){
if (xt && xml_child_nr(xt)){
x = NULL;
while ((x = xml_child_each(xt, x, -1)) != NULL) {
if (xmldb_put_tree(h, db, api_path, x, op) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
}
}
else
if (xmldb_put_tree(h, db, api_path, NULL, op) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;
}
}
else if (xmldb_put(h, db, xt, op) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto done;

View file

@ -645,6 +645,7 @@ load_config_file(clicon_handle h,
goto done;
if (clicon_rpc_xmlput(h, "candidate",
replace?OP_REPLACE:OP_MERGE,
"",
cbuf_get(cbxml)) < 0)
goto done;
cbuf_free(cbxml);

View file

@ -445,8 +445,7 @@ show_conf_as_json(clicon_handle h,
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)
xml2json(stdout, xc, 1);
xml2json(stdout, xt, 1);
retval = 0;
done:
if (xt)

View file

@ -198,7 +198,6 @@ netconf_filter_xmldb(clicon_handle h,
return retval;
}
/*! Get configuration
* @param[in] h Clicon handle
* @param[in] xorig Sub-tree (under xorig) at <get-config>...</> level.
@ -441,6 +440,7 @@ netconf_edit_config(clicon_handle h,
xmlstr = cbuf_get(cbxml);
if (clicon_rpc_xmlput(h, target,
operation,
"", /* api-path */
xmlstr) < 0){
netconf_create_rpc_error(cb_err, xorig,
"access-denied",

File diff suppressed because it is too large Load diff

View file

@ -24,27 +24,10 @@
* Prototypes
*/
int notfound(FCGX_Request *r);
char *ival2influxdb(char *ival);
int openfile(FCGX_Request *r, char *dir, char *filename);
int errorfn(FCGX_Request *r, char *root, char *reason);
int badrequest(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx);
int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
int netconf_rpc(cbuf *result, char *format, ...);
int netconf_cmd(FCGX_Request *r, char *data);
int cli_rpc(cbuf *result, char *mode, char *format, ...);
int cli_cmd(FCGX_Request *r, char *mode, char *cmd);
int check_credentials(char *passwd, cxobj *cx);
int get_db_entry(char *entry, char *attr, char *val, cxobj **cx);
int get_user_cookie(char *cookiestr, char *attribute, char **val);
int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r);
int create_database(char *server_addr, char *database, char *www_user, char *www_passwd);
int create_db_user(char *server_addr, char *database, char *user, char *password, char *www_user, char *www_passwd);
int url_post(char *url, char *username, char *passwd, char *putdata,
char *expect, char **getdata);
int b64_decode(const char *b64_buf, char *buf, size_t buf_len);
int metric_spec_description(char *metric, char **result);
int metric_spec_units(char *metric, char **result);
#endif /* _RESTCONF_LIB_H_ */

View file

@ -21,7 +21,7 @@
*/
/*
* See draft-ietf-netconf-restconf-13.txt
* See draft-ietf-netconf-restconf-13.txt [draft]
* sudo apt-get install libfcgi-dev
* gcc -o fastcgi fastcgi.c -lfcgi
@ -64,12 +64,12 @@
/*! Generic REST GET method
* @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] dvec Stream input data
* @param[in] pi Offset, where path starts
* @param[in] qvec Vector of query string (QUERY_STRING)
* @code
* curl -G http://localhost/api/data/profile/name=default/metric/rtt
* 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.
@ -78,6 +78,10 @@
* 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,
@ -85,20 +89,26 @@ api_data_get(clicon_handle h,
cvec *pcvec,
int pi,
cvec *qvec)
{
int retval = -1;
cg_var *cv;
char *val;
int i;
cbuf *path = NULL;
cbuf *path1 = NULL;
cxobj *xt = NULL;
cxobj *xg = NULL;
cbuf *cbx = NULL;
cxobj **vec = NULL;
size_t veclen;
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;
yspec = clicon_dbspec_yang(h);
clicon_debug(1, "%s", __FUNCTION__);
if ((path = cbuf_new()) == NULL)
goto done;
@ -109,53 +119,77 @@ api_data_get(clicon_handle h,
/* 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;
cprintf(path, "[%s=%s]", cv_name_get(cv), val);
free(val);
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);
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?"":"/"), cv_name_get(cv));
cprintf(path1, "/%s", cv_name_get(cv));
cprintf(path, "%s%s", (i==pi?"":"/"), name);
cprintf(path1, "/%s", name);
}
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
clicon_debug(1, "%s path1:%s", __FUNCTION__, cbuf_get(path1));
/* See netconf_rpc.c: 163 netconf_filter_xmldb() */
if (xmldb_get(h, "running", cbuf_get(path), 0, &xt, NULL, NULL) < 0)
clicon_debug(1, "path:%s", cbuf_get(path));
clicon_debug(1, "path1:%s", cbuf_get(path1));
if (xmldb_get(h, "running", cbuf_get(path), 0, &xt, &vec, &veclen) < 0)
goto done;
{
cbuf *cb;
cb = cbuf_new();
if (clicon_xml2cbuf(cb, xt, 0, 1) < 0)
goto done;
clicon_debug(1, "%s xt: %s", __FUNCTION__, cbuf_get(cb));
}
FCGX_SetExitStatus(200, r->out); /* OK */
FCGX_FPrintF(r->out, "Content-Type: application/yang.data+xml\r\n");
FCGX_FPrintF(r->out, "\r\n");
#if 0
/* Iterate over result */
if (xpath_vec(xt, cbuf_get(path1), &vec, &veclen) < 0)
goto done;
#endif
if ((cbx = cbuf_new()) == NULL)
goto done;
cprintf(cbx, "[\n");
for (i=0; i<veclen; i++){
xg = vec[i];
if (1){ /* JSON */
if (xml2json_cbuf(cbx, xg, 1) < 0)
goto done;
if (i<veclen-1)
cprintf(cbx, ",");
}
else
if (clicon_xml2cbuf(cbx, xg, 0, 0) < 0)
goto done;
}
cprintf(cbx, "]");
cprintf(cbx, "{\n");
if (xml2json_cbuf(cbx, xt, 1) < 0)
goto done;
cprintf(cbx, "}");
FCGX_FPrintF(r->out, "%s\r\n", cbuf_get(cbx));
retval = 0;
done:
@ -165,153 +199,146 @@ api_data_get(clicon_handle h,
cbuf_free(cbx);
if (xt)
xml_free(xt);
if (path)
cbuf_free(path);
if (path1)
cbuf_free(path1);
return retval;
}
/*! Generic REST PUT method
* @param[in] r Fastcgi request handle
* @param[in] pcvec Vector of path ie DOCUMENT_URI element
* @param[in] pi Offset, where to start pcvec
* Example:
* curl -X PUT -d enable=true http://localhost/api/data/profile=default/metric=rtt
*/
static int
api_data_put(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec,
cvec *dvec)
{
int retval = -1;
cg_var *cv;
int i;
char *val;
cbuf *cmd = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if ((cmd = cbuf_new()) == NULL)
goto done;
if (pi > cvec_len(pcvec)){
retval = notfound(r);
goto done;
}
cv = NULL;
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
cprintf(cmd, "%s ", cv_name_get(cv));
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
if (strlen(val))
cprintf(cmd, "%s ", val);
free(val);
}
}
if (cvec_len(dvec)==0)
goto done;
cv = cvec_i(dvec, 0);
cprintf(cmd, "%s ", cv_name_get(cv));
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
if (strlen(val))
cprintf(cmd, "%s ", val);
free(val);
}
clicon_debug(1, "cmd:%s", cbuf_get(cmd));
if (cli_cmd(r, "configure", cbuf_get(cmd)) < 0)
goto done;
if (cli_cmd(r, "configure", "commit") < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
done:
if (cmd)
cbuf_free(cmd);
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://localhost/api/data/profile=default/metric/rtt
* @note cant do leafs
* curl -X DELETE http://127.0.0.1/restconf/data/interfaces/interface=eth0
*/
static int
api_data_delete(clicon_handle h,
FCGX_Request *r,
cvec *pcvec,
int pi,
cvec *qvec)
char *api_path,
int pi)
{
int retval = -1;
cg_var *cv;
int i;
char *val;
cbuf *cmd = NULL;
int retval = -1;
int i;
clicon_debug(1, "%s", __FUNCTION__);
if ((cmd = cbuf_new()) == NULL)
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
for (i=0; i<pi; i++)
api_path = index(api_path+1, '/');
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
/* Parse input data as json into xml */
if (clicon_rpc_xmlput(h, "candidate",
OP_REMOVE,
api_path,
"") < 0)
goto done;
if (pi >= cvec_len(pcvec)){
retval = notfound(r);
clicon_debug(1, "%s xmldb_put ok", __FUNCTION__);
if (clicon_rpc_commit(h, "candidate", "running",
0, 0) < 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
* Example:
curl -X PUT -d {\"enabled\":\"false\"} http://127.0.0.1/restconf/data/interfaces/interface=eth1
* Problem: we have URI that defines a path (eg "interface/name=eth1") and data
* which defines a tree from that point.
* But, xmldb api can only do either
* - xmldb_put() with a complete xml-tree, or
* - xmldb_put_xkey for path and key value
* What we need is path and sub-xml tree.
* Alt1: parse URI to XML and and call xmldb_put()
* Alt2: Extend xmldb API with path + xml-tree.
*/
static int
api_data_put(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
char *data)
{
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, '/');
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
/* Parse input data as json into xml */
if (json_parse_str(data, &xdata) < 0){
clicon_debug(1, "%s json fail", __FUNCTION__);
goto done;
}
cprintf(cmd, "no ");
cv = NULL;
for (i=pi; i<cvec_len(pcvec); i++){
cv = cvec_i(pcvec, i);
cprintf(cmd, "%s ", cv_name_get(cv));
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
if (strlen(val))
cprintf(cmd, "%s ", val);
free(val);
}
}
clicon_debug(1, "cmd:%s", cbuf_get(cmd));
if (cli_cmd(r, "configure", cbuf_get(cmd)) < 0)
clicon_debug_xml(1, "json xml:", xdata);
if ((cbx = cbuf_new()) == NULL)
goto done;
if (cli_cmd(r, "configure", "commit") < 0)
x = NULL;
while ((x = xml_child_each(xdata, x, -1)) != NULL)
if (clicon_xml2cbuf(cbx, x, 0, 0) < 0)
goto done;
clicon_debug(1, "xml:%s", cbuf_get(cbx));
if (clicon_rpc_xmlput(h, "candidate",
OP_MERGE,
api_path,
cbuf_get(cbx)) < 0)
goto done;
clicon_debug(1, "%s xmldb_put ok", __FUNCTION__);
if (clicon_rpc_commit(h, "candidate", "running",
0, 0) < 0)
goto done;
FCGX_SetExitStatus(201, r->out);
FCGX_FPrintF(r->out, "Content-Type: text/plain\r\n");
FCGX_FPrintF(r->out, "\r\n");
done:
if (cmd)
cbuf_free(cmd);
return retval;
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (xdata)
xml_free(xdata);
if (cbx)
cbuf_free(cbx);
return retval;
}
/*! Generic REST method, GET, PUT, DELETE
* @param[in] h CLIXON handle
* @param[in] r Fastcgi request handle
* @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] dvec Stream input data
* @param[in] qvec Vector of query string (QUERY_STRING)
* data - implement restconf
* Eg:
* curl -X PUT -d enable=true http://localhost/api/data/profile=default/metric=rtt
* Uses cli, could have used netconf with some yang help.
* XXX But really this module should be a restconf module to clixon
* @param[in] dvec Stream input data
*/
static int
api_data(clicon_handle h,
FCGX_Request *r,
char *api_path,
cvec *pcvec,
int pi,
cvec *qvec,
cvec *dvec)
char *data)
{
int retval = -1;
char *request_method;
@ -321,15 +348,14 @@ api_data(clicon_handle h,
if (strcmp(request_method, "GET")==0)
retval = api_data_get(h, r, pcvec, pi, qvec);
else if (strcmp(request_method, "PUT")==0)
retval = api_data_put(h, r, pcvec, pi, qvec, dvec);
retval = api_data_put(h, r, api_path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "DELETE")==0)
retval = api_data_delete(h, r, pcvec, pi, qvec);
retval = api_data_delete(h, r, api_path, pi);
else
retval = notfound(r);
return retval;
}
/*! Process a FastCGI request
* @param[in] r Fastcgi request handle
*/
@ -370,7 +396,7 @@ request_process(clicon_handle h,
retval = 0;
test(r, 1);
if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
retval = api_data(h, r, pcvec, 2, qvec, dvec);
retval = api_data(h, r, path, pcvec, 2, qvec, data);
else if (strcmp(method, "test") == 0)
retval = test(r, 0);
else
@ -447,7 +473,6 @@ main(int argc,
argc -= optind;
argv += optind;
clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR);
clicon_log_init(__PROGRAM__, debug?LOG_DEBUG:LOG_INFO, CLICON_LOG_SYSLOG);
clicon_debug_init(debug, NULL);
@ -456,7 +481,7 @@ main(int argc,
goto done;
/* Parse yang database spec file */
if (yang_spec_main(h, stdout, 0) < 0)
if (yang_spec_main(h, NULL, 0) < 0)
goto done;
if ((sockpath = clicon_option_str(h, "CLICON_RESTCONF_PATH")) == NULL){

View file

@ -55,7 +55,8 @@ enum clicon_msg_type{
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: XML data
3. string: restconf api path
4. string: XML data
*/
CLICON_MSG_SAVE, /* Save config state from db to a file in backend. Body is:

View file

@ -33,7 +33,8 @@ int clicon_rpc_validate(clicon_handle h, char *db);
int clicon_rpc_change(clicon_handle h, char *db,
enum operation_type op, char *key, char *val);
int clicon_rpc_xmlput(clicon_handle h, char *db, enum operation_type op, char *xml);
int clicon_rpc_xmlput(clicon_handle h, char *db, enum operation_type op,
char *api_path, char *xml);
int clicon_rpc_dbitems(clicon_handle h, char *db, char *rx,
char *attr, char *val,
cvec ***cvv, size_t *cvvlen);

View file

@ -62,6 +62,7 @@ clicon_msg_change_decode(struct clicon_msg *msg,
struct clicon_msg *
clicon_msg_xmlput_encode(char *db,
uint32_t op,
char *api_path,
char *xml,
const char *label);
@ -69,7 +70,8 @@ int
clicon_msg_xmlput_decode(struct clicon_msg *msg,
char **db,
uint32_t *op,
char **filename,
char **api_path,
char **xml,
const char *label);
struct clicon_msg *

View file

@ -32,6 +32,8 @@ int xmlkeyfmt2xpath(char *xkfmt, cvec *cvv, char **xk);
int xmldb_get(clicon_handle h, char *db, char *xpath, int vector,
cxobj **xtop, cxobj ***xvec, size_t *xlen);
int xmldb_put(clicon_handle h, char *db, cxobj *xt, enum operation_type op);
int xmldb_put_tree(clicon_handle h, char *db, char *api_path,
cxobj *xt, enum operation_type op);
int xmldb_put_xkey(clicon_handle h, char *db,
char *xkey, char *val,
enum operation_type op);

View file

@ -188,6 +188,7 @@ yang_stmt *yang_find(yang_node *yn, int keyword, char *argument);
yang_stmt *yang_find_syntax(yang_node *yn, char *argument);
yang_stmt *yang_find_topnode(yang_spec *ysp, char *name);
int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal);
int yang_print(FILE *f, yang_node *yn, int marginal);
int yang_parse(clicon_handle h, const char *yang_dir,
const char *module, const char *revision, yang_spec *ysp);

View file

@ -105,15 +105,14 @@ OK, still something wrong with grafana plots
/* cligen */
#include <cligen/cligen.h>
/* clicon */
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_xml.h"
#include "clixon_json.h"
#include "clixon_json_parse.h"
#define INDENT 2 /* maybe we should set this programmatically? */
#define JSON_INDENT 3 /* maybe we should set this programmatically? */
/*! x is element and has eactly one child which in turn has none
* Clone from clixon_xml_map.c
@ -182,6 +181,9 @@ list_eval(cxobj *x)
/*!
* @param[in] pretty set if the output should be pretty-printed
* List only if adjacent,
* ie <a>1</a><a>2</a><b>3</b> -> {"a":[1,2],"b":3}
* ie <a>1</a><b>3</b><a>2</a> -> {"a":1,"b":3,"a":2}
@ -208,14 +210,33 @@ xml2json1_cbuf(cbuf *cb,
list = list_eval(x);
switch (list){
case LIST_NO:
cprintf(cb, "%*s\"%s\": ", pretty?(level*INDENT):0, "",xml_name(x));
cprintf(cb, "%*s\"%s\": ",
pretty?(level*JSON_INDENT):0, "",
xml_name(x));
if (!tleaf(x))
cprintf(cb, "{%s", pretty?"\n":"");
cprintf(cb, "{%s",
pretty?"\n":"");
break;
case LIST_FIRST:
cprintf(cb, "\"%*s\":[", pretty?(level*INDENT):0, xml_name(x));
cprintf(cb, "%*s\"%s\": [%s",
pretty?(level*JSON_INDENT):0, "",
xml_name(x),
pretty?"\n":"");
if (!tleaf(x)){
level++;
cprintf(cb, "%*s{%s",
pretty?(level*JSON_INDENT):0, "",
pretty?"\n":"");
}
break;
case LIST_MIDDLE:
case LIST_LAST:
level++;
cprintf(cb, "%*s",
pretty?(level*JSON_INDENT):0, "");
if (!tleaf(x))
cprintf(cb, "{%s", pretty?"\n":"");
cprintf(cb, "{%s",
pretty?"\n":"");
break;
default:
break;
@ -225,25 +246,33 @@ xml2json1_cbuf(cbuf *cb,
if (xml2json1_cbuf(cb, xc, level+1, pretty) < 0)
goto done;
if (i<xml_child_nr(x)-1){
cprintf(cb, ",");
cprintf(cb, ",", list);
cprintf(cb, "%s", pretty?"\n":"");
}
}
switch (list){
case LIST_NO:
if (!tleaf(x))
cprintf(cb, "%s%*s}%s",
pretty?"\n":"",
pretty?(level*INDENT):0,"",
pretty?"\n":"");
if (pretty)
cprintf(cb, "%*s}\n",
(level*JSON_INDENT), "");
break;
case LIST_MIDDLE:
case LIST_FIRST:
if (pretty)
cprintf(cb, "\n%*s}",
(level*JSON_INDENT), "");
break;
case LIST_LAST:
if (!tleaf(x))
cprintf(cb, "%s%*s}%s",
pretty?"\n":"",
pretty?(level*INDENT):0,"",
pretty?"\n":"");
cprintf(cb, "]%s",pretty?"\n":"");
if (!tleaf(x)){
if (pretty)
cprintf(cb, "\n%*s}\n",
(level*JSON_INDENT), "");
level--;
}
cprintf(cb, "%*s]%s",
pretty?(level*JSON_INDENT):0,"",
pretty?"\n":"");
break;
default:
break;
@ -261,7 +290,7 @@ xml2json1_cbuf(cbuf *cb,
*
* @param[in,out] cb Cligen buffer to write to
* @param[in] x XML tree to translate from
* @param[in] level Indentation level
* @param[in] pretty set if the output should be pretty-printed
* @retval 0 OK
* @retval -1 Error
*
@ -280,16 +309,15 @@ xml2json_cbuf(cbuf *cb,
int pretty)
{
int retval = 1;
int level = 1;
int level = 0;
int i;
cxobj *xc;
cprintf(cb, "%*s{%s",
pretty?(level*INDENT):0,"",
pretty?"\n":"");
if (xml2json1_cbuf(cb, x, level+1, pretty) < 0)
goto done;
cprintf(cb, "%*s}%s",
pretty?(level*INDENT):0,"",
pretty?"\n":"");
for (i=0; i<xml_child_nr(x); i++){
xc = xml_child_i(x, i);
if (xml2json1_cbuf(cb, xc, level, pretty) < 0)
goto done;
}
retval = 0;
done:
return retval;
@ -313,7 +341,7 @@ xml2json(FILE *f,
int pretty)
{
int retval = 1;
cbuf *cb;
cbuf *cb = NULL;
if ((cb = cbuf_new()) ==NULL){
clicon_err(OE_XML, errno, "cbuf_new");
@ -321,8 +349,11 @@ xml2json(FILE *f,
}
if (xml2json_cbuf(cb, x, pretty) < 0)
goto done;
fprintf(f, "%s", cbuf_get(cb));
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
@ -339,11 +370,11 @@ json_parse(char *str,
int retval = -1;
struct clicon_json_yacc_arg jy = {0,};
// clicon_debug(1, "%s", __FUNCTION__);
jy.jy_parse_string = str;
jy.jy_name = name;
jy.jy_linenum = 1;
jy.jy_current = xt;
if (json_scan_init(&jy) < 0)
goto done;
if (json_parse_init(&jy) < 0)
@ -354,9 +385,9 @@ json_parse(char *str,
clicon_err(OE_XML, 0, "JSON parser error with no error code (should not happen)");
goto done;
}
if (jy.jy_current)
xml_print(stdout, jy.jy_current);
retval = 0;
done:
// clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
json_parse_exit(&jy);
json_scan_exit(&jy);
return retval;

View file

@ -132,7 +132,7 @@ clixon_json_parseerror(void *_jy, char *s)
int
json_parse_init(struct clicon_json_yacc_arg *jy)
{
// clicon_debug_init(2, NULL);
// clicon_debug_init(2, NULL);
return 0;
}
@ -208,7 +208,7 @@ json_current_body(struct clicon_json_yacc_arg *jy,
*/
/* top: json -> value is also possible */
json : object J_EOF { clicon_debug(2,"json->object"); YYACCEPT; }
json : object J_EOF { clicon_debug(1,"json->object"); YYACCEPT; }
;
value : J_TRUE { json_current_body(_JY, "true");}
@ -221,16 +221,16 @@ value : J_TRUE { json_current_body(_JY, "true");}
;
object : '{' '}'
| '{' objlist '}'
object : '{' '}' { clicon_debug(2,"object->{}");}
| '{' objlist '}' { clicon_debug(2,"object->{ objlist }");}
;
objlist : pair
| objlist ',' pair
objlist : pair { clicon_debug(2,"objlist->pair");}
| objlist ',' pair { clicon_debug(2,"objlist->objlist , pair");}
;
pair : string { json_current_new(_JY, $1);free($1);} ':'
value { json_current_pop(_JY);}
value { json_current_pop(_JY);}{ clicon_debug(2,"pair->string : value");}
;
array : '[' ']'
@ -242,7 +242,7 @@ valuelist : value
;
/* quoted string */
string : J_DQ ustring J_DQ { $$=$2; }
string : J_DQ ustring J_DQ { clicon_debug(2,"string->\" ustring \"");$$=$2; }
;
/* unquoted string */

View file

@ -70,8 +70,10 @@ static const struct map_type2str msgmap[] = {
{CLICON_MSG_COMMIT, "commit"},
{CLICON_MSG_VALIDATE, "validate"},
{CLICON_MSG_CHANGE, "change"},
{CLICON_MSG_XMLPUT, "xmlput"},
{CLICON_MSG_SAVE, "save"},
{CLICON_MSG_LOAD, "load"},
{CLICON_MSG_COPY, "copy"},
{CLICON_MSG_KILL, "kill"},
{CLICON_MSG_DEBUG, "debug"},
{CLICON_MSG_CALL, "call"},

View file

@ -204,6 +204,7 @@ clicon_rpc_change(clicon_handle h,
* @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>
* @retval 0 OK
* @retval -1 Error
@ -212,6 +213,7 @@ int
clicon_rpc_xmlput(clicon_handle h,
char *db,
enum operation_type op,
char *api_path,
char *xml)
{
int retval = -1;
@ -219,6 +221,7 @@ clicon_rpc_xmlput(clicon_handle h,
if ((msg = clicon_msg_xmlput_encode(db,
(uint32_t)op,
api_path,
xml,
__FUNCTION__)) == NULL)
goto done;

View file

@ -266,16 +266,18 @@ clicon_msg_change_decode(struct clicon_msg *msg,
}
/*! Encode xmlput / edit of database content
* @param[in] db Name of database
* @param[in] op set|merge|delete. See lv_op_t
* @param[in] xml XML data string
* @param[in] label Memory chunk label
* @retval msg Encoded message
* @retval NULL Error
* @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)
{
@ -285,12 +287,12 @@ clicon_msg_xmlput_encode(char *db,
int p;
uint32_t tmp;
clicon_debug(2, "%s: op: %d db: %s xml: %s",
__FUNCTION__,
op, db, xml);
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(xml) + 1;
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;
@ -306,6 +308,8 @@ clicon_msg_xmlput_encode(char *db,
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;
@ -315,6 +319,7 @@ clicon_msg_xmlput_encode(char *db,
* @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
@ -324,6 +329,7 @@ int
clicon_msg_xmlput_decode(struct clicon_msg *msg,
char **db,
uint32_t *op,
char **api_path,
char **xml,
const char *label)
{
@ -342,20 +348,24 @@ clicon_msg_xmlput_decode(struct clicon_msg *msg,
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 xml: %s",
__FUNCTION__,
*op, *db, *xml);
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)

View file

@ -133,7 +133,10 @@ db_delete(char *file)
* @retval -1 on error
*/
int
db_set(char *file, char *key, void *data, size_t datalen)
db_set(char *file,
char *key,
void *data,
size_t datalen)
{
DEPOT *dp;
@ -310,7 +313,8 @@ db_del(char *file, char *key)
* @retval -1 error
*/
int
db_exists(char *file, char *key)
db_exists(char *file,
char *key)
{
DEPOT *dp;
int len;

View file

@ -34,7 +34,7 @@
/* cligen */
#include <cligen/cligen.h>
/* clicon */
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_queue.h"
@ -577,7 +577,7 @@ xml_insert(cxobj *xp,
}
/*! Remove and free an xml node child from xml parent
* @param[in] xch xml child node (to be removed and freed)
* @param[in] xc xml child node (to be removed and freed)
* @retval 0 OK
* @retval -1
* @note you cannot remove xchild in the loop (unless yoy keep track of xprev)
@ -681,6 +681,8 @@ xml_rm(cxobj *xc)
* @param[in] xp xml parent node. Will be deleted
* @param[in] i Child nr in parent child vector
* @param[out] xcp xml child node. New root
* @retval 0 OK
* @retval -1 Error
* @see xml_child_rm
*/
int
@ -845,7 +847,7 @@ xml_print(FILE *f,
return clicon_xml2file(f, xn, 0, 1);
}
#define XML_INDENT 3 /* maybve we should set this programmatically? */
#define XML_INDENT 3 /* maybe we should set this programmatically? */
/*! Print an XML tree structure to a cligen buffer
*

View file

@ -92,13 +92,14 @@
* | <aa><k>17</k></aa>| <------------- | /aa/17 |
* +-------------------+ +-----------------+
*
* ALternative for xmlkeyfmt would be xpath: eg
* Alternative for xmlkeyfmt would be xpath: eg
* instead of /interfaces/interface/%s/ipv4/address/ip/%s
* you can have: /interfaces/interface[name=%s]/ipv4/address/[ip=%s]
*/
/*! Recursive help function */
static int
yang2xmlkeyfmt_1(yang_stmt *ys, cbuf *cb)
yang2xmlkeyfmt_1(yang_stmt *ys,
cbuf *cb)
{
yang_node *yn;
yang_stmt *ykey;
@ -151,7 +152,8 @@ yang2xmlkeyfmt_1(yang_stmt *ys, cbuf *cb)
* @param[out] xpath String, needs to be freed after use
*/
int
yang2xmlkeyfmt(yang_stmt *ys, char **xkfmt)
yang2xmlkeyfmt(yang_stmt *ys,
char **xkfmt)
{
int retval = -1;
cbuf *cb = NULL;
@ -550,8 +552,8 @@ xml_tree_prune_unmarked(cxobj *xt,
}
/*!
* @param[in] xkey xmlkey
* @param[out] xt XML tree as result
* @param[in] xk xmlkey
* @param[out] xt XML tree as result
* XXX cannot handle top-level list
*/
static int
@ -976,14 +978,15 @@ xmldb_get_local(clicon_handle h,
}
/*! Get content of database using xpath.
/*! Get content of database using path.
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
* @param[in] h Clicon handle
* @param[in] db running | candidate
* @param[in] xpath String with XPATH syntax
* @param[in] vector If set, return list of results in xvec, else single tree.
* @param[out] xtop XML tree. Freed by xml_free()p
* @param[out] xtop XML tree. Freed by xml_free()
* @param[out] xvec Vector of xml trees. Free after use
* @param[out] xlen Length of vector.
* @retval 0 OK
@ -1081,6 +1084,11 @@ put(char *dbname,
yang_stmt *y;
int exists;
clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument);
if (debug){
xml_print(stderr, xt);
// yang_print(stderr, (yang_node*)ys, 0);
}
if (get_operation(xt, &op) < 0)
goto done;
body = xml_body(xt);
@ -1146,6 +1154,7 @@ put(char *dbname,
return retval;
}
/*! Local variant of xmldb_put */
static int
xmldb_put_local(clicon_handle h,
@ -1174,10 +1183,10 @@ xmldb_put_local(clicon_handle h,
goto done;
}
if (put(dbfilename, /* database name */
x, /* xml root node */
ys, /* yang statement of xml node */
op, /* operation, eg merge/delete */
"" /* aggregate xml key */
x, /* xml root node */
ys, /* yang statement of xml node */
op, /* operation, eg merge/delete */
"" /* aggregate xml key */
) < 0)
goto done;
}
@ -1201,10 +1210,12 @@ xmldb_put_local(clicon_handle h,
* The xml may contain the "operation" attribute which defines the operation.
* @code
* cxobj *xt;
* yang_spec *yspec = clicon_dbspec_yang(h);
* if (xmldb_put(dbname, xt, yspec, OP_MERGE) < 0)
* if (clicon_xml_parse_str("<a>17</a>", &xt) < 0)
* err;
* if (xmldb_put(h, "running", xt, OP_MERGE) < 0)
* err;
* @endcode
* @see xmldb_put_xkey for single key
*/
int
xmldb_put(clicon_handle h,
@ -1218,6 +1229,231 @@ xmldb_put(clicon_handle h,
return xmldb_put_local(h, db, xt, op);
}
/*! XXX: replace xk with xt
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
* e.g container top {
list list1 {
key "key1 key2 key3";
* is referenced as
/restconf/data/top/list1=a,,foo
*/
static int
xmldb_put_tree_local(clicon_handle h,
char *db,
char *api_path,
cxobj *xt,
enum operation_type op)
{
int retval = -1;
yang_stmt *y = NULL;
yang_stmt *ykey;
char **vec;
int nvec;
int i;
char *name;
cg_var *cvi;
cvec *cvk = NULL; /* vector of index keys */
char *val2;
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;
yang_stmt *ys;
char *filename = NULL;
char *key;
char *keys;
clicon_debug(1, "%s db:%s, api_path:%s", __FUNCTION__, db, api_path);
yspec = clicon_dbspec_yang(h);
if (db2file(h, db, &filename) < 0)
goto done;
if (api_path == NULL || *api_path!='/'){
clicon_err(OE_DB, 0, "Invalid api path: %s", api_path);
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(api_path, "/", &nvec, __FUNCTION__)) == NULL)
goto done;
if (nvec < 2){
clicon_err(OE_XML, 0, "Malformed key: %s", api_path);
goto done;
}
i = 1;
while (i<nvec){
name = vec[i];
if ((keys = index(name, '=')) != NULL){
*keys = '\0';
keys++;
}
if (i==1){
if (!strlen(name) && (op==OP_DELETE || op == OP_REMOVE)){
/* Special handling of "/" */
cprintf(ckey, "/%s", name);
break;
}
else
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;
}
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:
// val2 = vec[i]; /* No */
val2 = keys;
if (i>=nvec){
clicon_err(OE_XML, errno, "Leaf-list %s without argument", name);
goto done;
}
// i++;
cprintf(ckey, "/%s", val2);
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;
cvi = NULL;
/* Iterate over individual yang keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
// val2 = vec[i++]; /* No */
val2 = keys;
if (i>nvec){ /* XXX >= ? */
clicon_err(OE_XML, errno, "List %s without argument", name);
goto done;
}
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;
}
}
key = cbuf_get(ckey);
/* final key */
switch (op){
case OP_CREATE:
if ((exists = db_exists(filename, key)) < 0)
goto done;
if (exists == 1){
clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", key);
goto done;
}
case OP_MERGE:
case OP_REPLACE:
if (xt==NULL){
clicon_err(OE_DB, 0, "%s: no xml when yang node %s required",
__FUNCTION__, y->ys_argument);
goto done;
}
if ((ys = yang_find_syntax((yang_node*)y, xml_name(xt))) == NULL){
clicon_err(OE_DB, 0, "%s: child %s not found under node %s",
__FUNCTION__, xml_name(xt), y->ys_argument);
goto done;
}
y = ys;
if (put(filename, xt, y, op, key) < 0)
goto done;
break;
case OP_DELETE:
if ((exists = db_exists(filename, key)) < 0)
goto done;
if (exists == 0){
clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", key);
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.*$", key);
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:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
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;
}
/*!
* @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13])
*/
int
xmldb_put_tree(clicon_handle h,
char *db,
char *api_path,
cxobj *xt,
enum operation_type op)
{
if (clicon_xmldb_rpc(h))
return -1; /* XXX */
else
return xmldb_put_tree_local(h, db, api_path, xt, op);
}
/*! Local variant of xmldb_put_xkey */
static int
xmldb_put_xkey_local(clicon_handle h,
@ -1416,6 +1652,7 @@ xmldb_put_xkey_local(clicon_handle h,
* if (xmldb_put_xkey(h, db, "/aa/bb/17/name", "17", OP_MERGE) < 0)
* err;
* @endcode
* @see xmldb_put with xml-tree, no path
*/
int
xmldb_put_xkey(clicon_handle h,

View file

@ -550,6 +550,8 @@ ys_module(yang_stmt *ys)
{
yang_node *yn;
if (ys==NULL || ys->ys_keyword==Y_SPEC)
return ys;
while (ys != NULL && ys->ys_keyword != Y_MODULE && ys->ys_keyword != Y_SUBMODULE){
yn = ys->ys_parent;
/* Some extra stuff to ensure ys is a stmt */
@ -650,6 +652,9 @@ quotedstring(char *s)
return i < len;
}
/*! Print yang specification to file
* @see yang_print_cbuf
*/
int
yang_print(FILE *f,
yang_node *yn,
@ -659,6 +664,7 @@ yang_print(FILE *f,
while ((ys = yn_each(yn, ys)) != NULL) {
fprintf(f, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword));
fflush(f);
if (ys->ys_argument){
if (quotedstring(ys->ys_argument))
fprintf(f, " \"%s\"", ys->ys_argument);
@ -676,6 +682,41 @@ yang_print(FILE *f,
return 0;
}
/*! Print yang specification to cligen buf
* @code
* cbuf *cb = cbuf_new();
* yang_print_cbuf(cb, yn, 0);
* // output is in cbuf_buf(cb);
* cbuf_free(cb);
* @endcode
* @see yang_print
*/
int
yang_print_cbuf(cbuf *cb,
yang_node *yn,
int marginal)
{
yang_stmt *ys = NULL;
while ((ys = yn_each(yn, ys)) != NULL) {
cprintf(cb, "%*s%s", marginal, "", yang_key2str(ys->ys_keyword));
if (ys->ys_argument){
if (quotedstring(ys->ys_argument))
cprintf(cb, " \"%s\"", ys->ys_argument);
else
cprintf(cb, " %s", ys->ys_argument);
}
if (ys->ys_len){
cprintf(cb, " {\n");
yang_print_cbuf(cb, (yang_node*)ys, marginal+3);
cprintf(cb, "%*s%s\n", marginal, "", "}");
}
else
cprintf(cb, ";\n");
}
return 0;
}
/*! Populate yang leafs after parsing. Create cv and fill it in.
*
@ -1671,7 +1712,7 @@ yang_xpath_vec(yang_node *yn,
else
id++;
if ((ys = yang_find_xpath_stmt(yn, id)) == NULL){
clicon_debug(0, "%s %s not found", __FUNCTION__, id);
clicon_debug(1, "%s %s not found", __FUNCTION__, id);
goto done;
}
}
@ -1737,12 +1778,15 @@ yang_xpath_abs(yang_node *yn,
clicon_err(OE_YANG, errno, "%s: strsplit", __FUNCTION__);
return NULL;
}
/* Assume path looks like: "/prefix:id[/prefix:id]*" */
if (nvec < 2){
clicon_err(OE_YANG, 0, "%s: NULL or truncated path: %s",
__FUNCTION__, xpath);
goto done;
}
/* Check for absolute path */
if (strcmp(vec[0], "") != 0){
clicon_err(OE_YANG, errno, "%s: %s: expected absolute path",
@ -1750,10 +1794,14 @@ yang_xpath_abs(yang_node *yn,
goto done;
}
/* Find correct module */
ymod = ys_module((yang_stmt*)yn); /* This is current module */
if ((ymod = ys_module((yang_stmt*)yn)) == NULL){
clicon_err(OE_YANG, 0, "Could not find top-level");
goto done;
}
/* split <prefix>:<id> */
if ((id = strchr(vec[1], ':')) == NULL)
if ((id = strchr(vec[1], ':')) == NULL){
id = vec[1];
}
else{ /* other module - peek into first element to find module */
if ((prefix = strdup(vec[1])) == NULL){
clicon_err(OE_UNIX, errno, "%s: strdup", __FUNCTION__);
@ -1931,7 +1979,7 @@ yang_config(yang_stmt *ys)
/*! Utility function for handling yang parsing and translation to key format
* @param h clicon handle
* @param f file to print to (if one of print options are enabled)
* @param f file to print to (if printspec enabled)
* @param printspec print database (YANG) specification as read from file
*/
int