restconf; xmlput extended with api_path; xmldb_put_tree
This commit is contained in:
parent
ebd53a34ee
commit
b744a4ad8a
21 changed files with 732 additions and 1260 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -24,71 +24,7 @@
|
|||
|
||||
#include "restconf_lib.h"
|
||||
|
||||
#define signal_set_mask(set) sigprocmask(SIG_SETMASK, (set), NULL)
|
||||
#define signal_get_mask(set) sigprocmask (0, NULL, (set))
|
||||
|
||||
/* Some hardcoded paths */
|
||||
#define CLI_BIN "/usr/local/bin/clixon_cli"
|
||||
#define CLI_OPTS "-1 -q"
|
||||
|
||||
/*
|
||||
* Types (curl)
|
||||
*/
|
||||
struct curlbuf{
|
||||
size_t b_len;
|
||||
char *b_buf;
|
||||
};
|
||||
|
||||
#ifdef notused
|
||||
static int _log = 0; /* to log or not to log */
|
||||
/*!
|
||||
*/
|
||||
int
|
||||
dbg_init(char *ident)
|
||||
{
|
||||
openlog(ident, LOG_PID, LOG_USER);
|
||||
_log++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
*/
|
||||
int
|
||||
dbg(char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
int len;
|
||||
int retval = -1;
|
||||
char *msg = NULL;
|
||||
|
||||
if (_log == 0)
|
||||
return 0;
|
||||
/* first round: compute length of debug message */
|
||||
va_start(args, format);
|
||||
len = vsnprintf(NULL, 0, format, args);
|
||||
va_end(args);
|
||||
/* allocate a message string exactly fitting the message length */
|
||||
if ((msg = malloc(len+1)) == NULL){
|
||||
fprintf(stderr, "malloc: %s\n", strerror(errno)); /* dont use clicon_err here due to recursion */
|
||||
goto done;
|
||||
}
|
||||
/* second round: compute write message from format and args */
|
||||
va_start(args, format);
|
||||
if (vsnprintf(msg, len+1, format, args) < 0){
|
||||
va_end(args);
|
||||
fprintf(stderr, "vsnprintf: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
va_end(args);
|
||||
syslog(LOG_MAKEPRI(LOG_USER, LOG_DEBUG), "%s", msg);
|
||||
retval = 0;
|
||||
done:
|
||||
if (msg)
|
||||
free(msg);
|
||||
return retval;
|
||||
}
|
||||
#endif /* notused */
|
||||
/*!
|
||||
*/
|
||||
int
|
||||
|
|
@ -100,203 +36,47 @@ notfound(FCGX_Request *r)
|
|||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 404\r\n"); /* 404 not found */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Grideye Not Found</h1>\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Clixon Not Found</h1>\n");
|
||||
FCGX_FPrintF(r->out, "The requested URL %s was not found on this server.\n",
|
||||
path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*! Map from keywords grideye config to influxdb interval format
|
||||
*/
|
||||
char *
|
||||
ival2influxdb(char *ival)
|
||||
{
|
||||
if (strcmp(ival, "minute")==0)
|
||||
return "1m";
|
||||
else if (strcmp(ival, "hour")==0)
|
||||
return "1h";
|
||||
else if (strcmp(ival, "day")==0)
|
||||
return "1d";
|
||||
else if (strcmp(ival, "week")==0)
|
||||
return "7d"; /* 1w is sunday to sunday */
|
||||
else if (strcmp(ival, "month")==0)
|
||||
return "30d";
|
||||
else if (strcmp(ival, "year")==0 || strcmp(ival, "all")==0)
|
||||
return "365d";
|
||||
return "1w";
|
||||
}
|
||||
|
||||
/* ripped from clicon_proc.c: clicon_proc_run()
|
||||
* @inparam[in] instr pipe to process stdin.
|
||||
*/
|
||||
static int
|
||||
proc_run(char *cmd,
|
||||
char *instr,
|
||||
void (outcb)(char *, void *),
|
||||
void *arg)
|
||||
{
|
||||
char **argv;
|
||||
char buf[512];
|
||||
int outfd[2] = { -1, -1 };
|
||||
int infd[2] = { -1, -1 };
|
||||
int n;
|
||||
int argc;
|
||||
int status;
|
||||
int retval = -1;
|
||||
pid_t child;
|
||||
sigfn_t oldhandler = NULL;
|
||||
sigset_t oset;
|
||||
|
||||
clicon_debug(1, "%s %s", __FUNCTION__, cmd);
|
||||
argv = clicon_sepsplit (cmd, " \t", &argc, __FUNCTION__);
|
||||
if (!argv)
|
||||
return -1;
|
||||
|
||||
if (pipe (outfd) == -1)
|
||||
goto done;
|
||||
if (pipe (infd) == -1)
|
||||
goto done;
|
||||
|
||||
signal_get_mask(&oset);
|
||||
// set_signal(SIGINT, clicon_proc_sigint, &oldhandler);
|
||||
|
||||
if ((child = fork ()) < 0) {
|
||||
retval = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (child == 0) { /* Child */
|
||||
|
||||
/* Unblock all signals except TSTP */
|
||||
clicon_signal_unblock (0);
|
||||
signal (SIGTSTP, SIG_IGN);
|
||||
|
||||
close (outfd[0]); /* Close unused read ends */
|
||||
outfd[0] = -1;
|
||||
|
||||
close (infd[1]); /* Close unused read ends */
|
||||
infd[1] = -1;
|
||||
|
||||
/* Divert stdout and stderr to pipes */
|
||||
dup2 (outfd[1], STDOUT_FILENO);
|
||||
if (0)
|
||||
dup2 (outfd[1], STDERR_FILENO);
|
||||
|
||||
dup2 (infd[0], STDIN_FILENO);
|
||||
execvp (argv[0], argv);
|
||||
perror("execvp");
|
||||
_exit(-1);
|
||||
}
|
||||
|
||||
/* Parent */
|
||||
/* Close unused read ends */
|
||||
close (infd[0]);
|
||||
infd[0] = -1;
|
||||
if (instr){
|
||||
if (write(infd[1], instr, strlen(instr)) < 0){
|
||||
perror("write");
|
||||
goto done;
|
||||
}
|
||||
close(infd[1]);
|
||||
}
|
||||
|
||||
/* Close unused write ends */
|
||||
close (outfd[1]);
|
||||
outfd[1] = -1;
|
||||
/* Read from pipe */
|
||||
while ((n = read (outfd[0], buf, sizeof (buf)-1)) != 0) {
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
/* Pass read data to callback function is defined */
|
||||
if (outcb)
|
||||
outcb (buf, arg);
|
||||
}
|
||||
/* Wait for child to finish */
|
||||
if(waitpid (child, &status, 0) == child)
|
||||
retval = WEXITSTATUS(status);
|
||||
else
|
||||
retval = -1;
|
||||
done:
|
||||
|
||||
/* Clean up all pipes */
|
||||
if (outfd[0] != -1)
|
||||
close (outfd[0]);
|
||||
if (outfd[1] != -1)
|
||||
close (outfd[1]);
|
||||
|
||||
/* Restore sigmask and fn */
|
||||
signal_set_mask (&oset);
|
||||
set_signal(SIGINT, oldhandler, NULL);
|
||||
|
||||
unchunk_group (__FUNCTION__);
|
||||
clicon_debug(1, "%s end %d", __FUNCTION__, retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Open dir/filename and pipe to fast-cgi output
|
||||
* @param[in] r Fastcgi request handle
|
||||
* Either absolute path in filename _or_ concatenation of dir/filename.
|
||||
*/
|
||||
int
|
||||
openfile(FCGX_Request *r,
|
||||
char *dir,
|
||||
char *filename)
|
||||
badrequest(FCGX_Request *r)
|
||||
{
|
||||
char *path;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
path = FCGX_GetParam("DOCUMENT_URI", r->envp);
|
||||
FCGX_FPrintF(r->out, "Status: 400\r\n"); /* 400 bad request */
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n");
|
||||
FCGX_FPrintF(r->out, "<h1>Clixon Bad request/h1>\n");
|
||||
FCGX_FPrintF(r->out, "The requested URL %s or data is in some way badly formed.\n",
|
||||
path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Specialization of clicon_debug with xml tree */
|
||||
int
|
||||
clicon_debug_xml(int dbglevel,
|
||||
char *str,
|
||||
cxobj *x)
|
||||
{
|
||||
int retval = -1;
|
||||
char buf[512];
|
||||
int f;
|
||||
int n;
|
||||
cbuf *cb = NULL;
|
||||
cbuf *cb;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
if (dir)
|
||||
cprintf(cb, "%s/%s", dir, filename);
|
||||
else
|
||||
cprintf(cb, "%s", filename);
|
||||
clicon_debug(1, "%s: %s", __FUNCTION__, cbuf_get(cb));
|
||||
if ((f = open(cbuf_get(cb), O_RDONLY)) < 0){
|
||||
clicon_debug(1, "open error: %s", strerror(errno));
|
||||
perror("open");
|
||||
if (clicon_xml2cbuf(cb, x, 0, 0) < 0)
|
||||
goto done;
|
||||
}
|
||||
while ((n = read(f, buf, sizeof (buf)-1)) != 0) {
|
||||
if (n < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
perror("read");
|
||||
goto done;
|
||||
}
|
||||
buf[n] = '\0';
|
||||
FCGX_FPrintF(r->out, "%s", buf);
|
||||
}
|
||||
clicon_debug(1, "%s %s", str, cbuf_get(cb));
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb!=NULL)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param[in] r Fastcgi request handle
|
||||
*/
|
||||
int
|
||||
errorfn(FCGX_Request *r,
|
||||
char *root,
|
||||
char *reason)
|
||||
{
|
||||
FCGX_FPrintF(r->out, "Content-Type: text/html\r\n");
|
||||
FCGX_FPrintF(r->out, "\r\n");
|
||||
openfile(r, root, "www/login.html"); /* "user not specified" */
|
||||
FCGX_FPrintF(r->out, "<div class=\"errbox\">%s</div>\r\n", reason);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Split a string into a cligen variable vector using 1st and 2nd delimiter
|
||||
* Split a string first into elements delimited by delim1, then into
|
||||
* pairs delimited by delim2.
|
||||
|
|
@ -387,263 +167,6 @@ str2cvec(char *string,
|
|||
goto done;
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
static void
|
||||
appendfn(char *str,
|
||||
void *arg)
|
||||
{
|
||||
cbuf *cb = (cbuf *)arg;
|
||||
|
||||
cprintf(cb, "%s", str);
|
||||
}
|
||||
|
||||
/*!
|
||||
*/
|
||||
static void
|
||||
outputfn(char *str,
|
||||
void *arg)
|
||||
{
|
||||
FCGX_Request *r = (FCGX_Request *)arg;
|
||||
|
||||
FCGX_FPrintF(r->out, "%s", str);
|
||||
clicon_debug(1, "%s: %s", __FUNCTION__, str);
|
||||
}
|
||||
|
||||
/*! Send an RPC to netconf client and return result as string
|
||||
* param[out] result output from cli as cligen buf
|
||||
* param[in] format stdarg variable list format a la printf followed by args
|
||||
*/
|
||||
int
|
||||
netconf_rpc(cbuf *result,
|
||||
char *format, ...)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
va_list args;
|
||||
int len;
|
||||
char *data = NULL;
|
||||
char *endtag;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
va_start(args, format);
|
||||
len = vsnprintf(NULL, 0, format, args);
|
||||
va_end(args);
|
||||
if ((data = malloc(len+1)) == NULL){
|
||||
fprintf(stderr, "malloc: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
va_start(args, format);
|
||||
vsnprintf(data, len+1, format, args);
|
||||
va_end(args);
|
||||
cprintf(cb, "%s -f %s %s",
|
||||
NETCONF_BIN, CONFIG_FILE, NETCONF_OPTS);
|
||||
clicon_debug(1, "%s: cmd:%s", __FUNCTION__, cbuf_get(cb));
|
||||
clicon_debug(1, "%s: data=%s", __FUNCTION__, data);
|
||||
if (proc_run(cbuf_get(cb), data, appendfn, result) < 0)
|
||||
goto done;
|
||||
if ((endtag = strstr(cbuf_get(result), "]]>]]>")) != NULL)
|
||||
*endtag = '\0';
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (data)
|
||||
free(data);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! send netconf command and return output to fastcgi request
|
||||
* @param[in] r Fastcgi request handle
|
||||
*/
|
||||
int
|
||||
netconf_cmd(FCGX_Request *r,
|
||||
char *data)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cb, "%s -f %s %s", NETCONF_BIN, CONFIG_FILE, NETCONF_OPTS);
|
||||
if (proc_run(cbuf_get(cb), data, outputfn, r) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Send an rpc to cli client and return result as string
|
||||
* param[out] result output from cli as cligen buf
|
||||
* param[in] mode CLI mode, typically "configure" or "operation"
|
||||
* param[in] format stdarg variable list format a la printf followed by args
|
||||
*/
|
||||
int
|
||||
cli_rpc(cbuf *result,
|
||||
char *mode,
|
||||
char *format, ...)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
va_list args;
|
||||
int len;
|
||||
char *cmd = NULL;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
va_start(args, format);
|
||||
len = vsnprintf(NULL, 0, format, args);
|
||||
va_end(args);
|
||||
if ((cmd = malloc(len+1)) == NULL){
|
||||
fprintf(stderr, "malloc: %s\n", strerror(errno));
|
||||
goto done;
|
||||
}
|
||||
va_start(args, format);
|
||||
vsnprintf(cmd, len+1, format, args);
|
||||
va_end(args);
|
||||
cprintf(cb, "%s -f %s -m %s %s -- %s",
|
||||
CLI_BIN, CONFIG_FILE, mode, CLI_OPTS, cmd);
|
||||
if (proc_run(cbuf_get(cb), NULL, appendfn, result) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
if (cmd)
|
||||
free(cmd);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! send cli command and return output to fastcgi request
|
||||
* @param[in] r Fastcgi request handle
|
||||
* @param[in] mode Command mode, eg configure, operation
|
||||
* @param[in] cmd Command to run in CLI
|
||||
*/
|
||||
int
|
||||
cli_cmd(FCGX_Request *r,
|
||||
char *mode,
|
||||
char *cmd)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
|
||||
if ((cb = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
cprintf(cb, "%s -f %s -m %s %s -- %s",
|
||||
CLI_BIN, CONFIG_FILE, mode, CLI_OPTS, cmd);
|
||||
if (proc_run(cbuf_get(cb), NULL, outputfn, r) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Check in db if user exists, then if passwd matches or if passwd == null
|
||||
* @param[in] passwd Passwd. If NULL dont do passwd check
|
||||
* @param[in] cx Parse-tree with matching user
|
||||
*/
|
||||
int
|
||||
check_credentials(char *passwd,
|
||||
cxobj *cx)
|
||||
{
|
||||
int retval = 0;
|
||||
cxobj *x;
|
||||
|
||||
if ((x = xpath_first(cx, "//user/resultdb/password")) == NULL)
|
||||
goto done;
|
||||
clicon_debug(1, "%s passwd=%s", __FUNCTION__, xml_body(x));
|
||||
if (passwd == NULL || strcmp(passwd, xml_body(x))==0)
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Get matching top-level list entry given an attribute value pair, eg name="foo"
|
||||
* Construct an XPATH query and send to netconf clixon client to get config xml.
|
||||
* The xpath is canonically formed as: //<entry>[<attr>=<val>]
|
||||
* @param[in] entry XPATH base path
|
||||
* @param[in] attr Attribute name, if not given skip the [addr=val]
|
||||
* @param[in] val Value of attribute
|
||||
* @param[out] cx This is an xml tree containing the result
|
||||
*/
|
||||
int
|
||||
get_db_entry(char *entry,
|
||||
char *attr,
|
||||
char *val,
|
||||
cxobj **cx)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *resbuf = NULL;
|
||||
char *xmlstr;
|
||||
|
||||
clicon_debug(1, "%s //%s[%s=%s]", __FUNCTION__, entry, attr, val);
|
||||
if ((resbuf = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
if (attr){
|
||||
if (netconf_rpc(resbuf,
|
||||
"<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"//%s[%s=%s]\" /></get-config></rpc>]]>]]>",
|
||||
entry, attr, val) < 0){
|
||||
clicon_debug(1, "%s error", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (netconf_rpc(resbuf,
|
||||
"<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"//%s\" /></get-config></rpc>]]>]]>",
|
||||
entry) < 0){
|
||||
clicon_debug(1, "%s error", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
xmlstr = cbuf_get(resbuf);
|
||||
// clicon_debug(1, "%s: %s\n", __FUNCTION__, xmlstr);
|
||||
if (clicon_xml_parse_string(&xmlstr, cx) < 0){
|
||||
clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
|
||||
goto done;
|
||||
}
|
||||
if (*cx == NULL)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (resbuf)
|
||||
cbuf_free(resbuf);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Parse a cookie string and return value of cookie attribute
|
||||
* @param[in] cookiestr cookie string according to rfc6265 (modified)
|
||||
* @param[in] attribute cookie attribute
|
||||
* @param[out] val malloced cookie value, free with free()
|
||||
*/
|
||||
int
|
||||
get_user_cookie(char *cookiestr,
|
||||
char *attribute,
|
||||
char **val)
|
||||
{
|
||||
int retval = -1;
|
||||
cvec *cvv = NULL;
|
||||
char *c;
|
||||
|
||||
clicon_debug(1, "%s cookiestr=%s", __FUNCTION__, cookiestr);
|
||||
if (str2cvec(cookiestr, ';', '=', &cvv) < 0)
|
||||
goto done;
|
||||
if ((c = cvec_find_str(cvv, attribute)) != NULL){
|
||||
if ((*val = strdup(c)) == NULL)
|
||||
goto done;
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (cvv)
|
||||
cvec_free(cvv);
|
||||
clicon_debug(1, "%s end %d", __FUNCTION__, retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @param[in] r Fastcgi request handle
|
||||
*/
|
||||
|
|
@ -707,427 +230,3 @@ readdata(FCGX_Request *r)
|
|||
return cb;
|
||||
}
|
||||
|
||||
|
||||
/*! Create influxdb database
|
||||
* @param[in] server_addr Name of http server. dns or ip
|
||||
* @param[in] database Name of database to create
|
||||
* @param[in] www_user Root admin user
|
||||
* @param[in] www_passwd Password of root user
|
||||
*/
|
||||
int
|
||||
create_database(char *server_addr,
|
||||
char *database,
|
||||
char *www_user,
|
||||
char *www_passwd)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *curl = NULL;
|
||||
cbuf *cdata = NULL;
|
||||
|
||||
if ((curl = cbuf_new()) == NULL) /* url */
|
||||
goto done;
|
||||
if ((cdata = cbuf_new()) == NULL) /* data */
|
||||
goto done;
|
||||
cprintf(curl, "http://%s:8086/db", server_addr);
|
||||
cprintf(cdata, "{\"name\": \"%s\"}", database);
|
||||
clicon_debug(1, "%s: %s %s", __FUNCTION__, cbuf_get(curl), cbuf_get(cdata));
|
||||
if (url_post(cbuf_get(curl), www_user, www_passwd, cbuf_get(cdata), NULL, NULL) < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
done:
|
||||
if (curl)
|
||||
cbuf_free(curl);
|
||||
if (cdata)
|
||||
cbuf_free(cdata);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Create user passwd in database
|
||||
* @param[in] server_addr Name of http server. dns or ip
|
||||
* @param[in] database Name of database to create
|
||||
* @param[in] user User to create for database (not root)
|
||||
* @param[in] passwd Password of user
|
||||
* @param[in] www_user Root user to create databases
|
||||
* @param[in] www_passwd Password of root user
|
||||
*/
|
||||
int
|
||||
create_db_user(char *server_addr,
|
||||
char *database,
|
||||
char *user,
|
||||
char *password,
|
||||
char *www_user,
|
||||
char *www_passwd)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *curl = NULL;
|
||||
cbuf *cdata = NULL;
|
||||
|
||||
if ((curl = cbuf_new()) == NULL) /* url */
|
||||
goto done;
|
||||
if ((cdata = cbuf_new()) == NULL) /* data */
|
||||
goto done;
|
||||
cprintf(curl, "http://%s:8086/db/%s/users", server_addr, database);
|
||||
cprintf(cdata, "{\"name\": \"%s\", \"password\": \"%s\"}",
|
||||
user, password);
|
||||
if (url_post(cbuf_get(curl), www_user, www_passwd, cbuf_get(cdata), NULL, NULL) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s: %s %s", __FUNCTION__, cbuf_get(curl), cbuf_get(cdata));
|
||||
cbuf_reset(curl);
|
||||
cbuf_reset(cdata);
|
||||
cprintf(curl, "http://%s:8086/db/%s/users/%s", server_addr, database, user);
|
||||
cprintf(cdata, "{\"admin\": true}", password);
|
||||
if (url_post(cbuf_get(curl), www_user, www_passwd, cbuf_get(cdata), NULL, NULL) < 0)
|
||||
goto done;
|
||||
clicon_debug(1, "%s: %s %s", __FUNCTION__, cbuf_get(curl), cbuf_get(cdata));
|
||||
retval = 0;
|
||||
done:
|
||||
if (curl)
|
||||
cbuf_free(curl);
|
||||
if (cdata)
|
||||
cbuf_free(cdata);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Send a curl POST request
|
||||
* @retval -1 fatal error
|
||||
* @retval 0 expect set but did not expected return or other non-fatal error
|
||||
* @retval 1 ok
|
||||
* Note: curl_easy_perform blocks
|
||||
* Note: New handle is created every time, the handle can be re-used for better TCP performance
|
||||
* @see same function (url_post) in grideye_curl.c
|
||||
*/
|
||||
int
|
||||
url_post(char *url,
|
||||
char *username,
|
||||
char *passwd,
|
||||
char *putdata,
|
||||
char *expect,
|
||||
char **getdata)
|
||||
{
|
||||
CURL *curl = NULL;
|
||||
char *err;
|
||||
int retval = -1;
|
||||
cxobj *xr = NULL; /* reply xml */
|
||||
struct curlbuf b = {0, };
|
||||
CURLcode errcode;
|
||||
char *output = NULL;
|
||||
|
||||
/* Try it with curl -X PUT -d '*/
|
||||
clicon_debug(1, "%s: curl -X POST -d '%s' %s (%s:%s)",
|
||||
__FUNCTION__, putdata, url, username, passwd);
|
||||
/* Set up curl for doing the communication with the controller */
|
||||
if ((curl = curl_easy_init()) == NULL) {
|
||||
clicon_debug(1, "curl_easy_init");
|
||||
goto done;
|
||||
}
|
||||
if ((output = curl_easy_escape(curl, putdata, 0)) == NULL){
|
||||
clicon_debug(1, "curl_easy_escape");
|
||||
goto done;
|
||||
}
|
||||
if ((err = chunk(CURL_ERROR_SIZE, __FUNCTION__)) == NULL) {
|
||||
clicon_debug(1, "%s: chunk", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url);
|
||||
if (username)
|
||||
curl_easy_setopt(curl, CURLOPT_USERNAME, username);
|
||||
if (passwd)
|
||||
curl_easy_setopt(curl, CURLOPT_PASSWORD, passwd);
|
||||
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err);
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, putdata);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(putdata));
|
||||
if (debug)
|
||||
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
||||
if ((errcode = curl_easy_perform(curl)) != CURLE_OK){
|
||||
clicon_debug(1, "%s: curl: %s(%d)", __FUNCTION__, err, errcode);
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
if (expect){
|
||||
if (b.b_buf == NULL){
|
||||
clicon_debug(1, "%s: no match", __FUNCTION__);
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
clicon_debug(1, "%s: reply:%s", __FUNCTION__, b.b_buf);
|
||||
if (clicon_xml_parse_string(&b.b_buf, &xr) < 0)
|
||||
goto done;
|
||||
if (xpath_first(xr, expect) == NULL){
|
||||
clicon_debug(1, "%s: no match", __FUNCTION__);
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getdata && b.b_buf){
|
||||
*getdata = b.b_buf;
|
||||
b.b_buf = NULL;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
unchunk_group(__FUNCTION__);
|
||||
if (output)
|
||||
curl_free(output);
|
||||
if (xr != NULL)
|
||||
xml_free(xr);
|
||||
if (b.b_buf)
|
||||
free(b.b_buf);
|
||||
if (curl)
|
||||
curl_easy_cleanup(curl); /* cleanup */
|
||||
return retval;
|
||||
}
|
||||
|
||||
static const char Base64[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
static const char Pad64 = '=';
|
||||
|
||||
#if notdef
|
||||
/*! return length of dst */
|
||||
int
|
||||
b64_encode(const char *src,
|
||||
char *dst,
|
||||
int dlen)
|
||||
{
|
||||
int i;
|
||||
int s = 0;
|
||||
int j = 0;
|
||||
uint32_t data = 0;
|
||||
int slen;
|
||||
|
||||
slen = strlen(src);
|
||||
/*
|
||||
* The encoded length will "grow" 33% in respect to the original length,
|
||||
* i.e. 3 character will be 4 characters
|
||||
*/
|
||||
if (((slen * 7) / 5) > dlen) {
|
||||
clicon_debug(1, "Destination buffer to small.\n");
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < slen; i++) {
|
||||
data <<= 8;
|
||||
data |= ((uint32_t)src[i] & 0x000000ff);
|
||||
s++;
|
||||
if (s == 3) {
|
||||
dst[j++] = Base64[((data >> 18) & 0x3f)];
|
||||
dst[j++] = Base64[((data >> 12) & 0x3f)];
|
||||
dst[j++] = Base64[((data >> 6) & 0x3f)];
|
||||
dst[j++] = Base64[(data & 0x3f)];
|
||||
dst[j] = '\0';
|
||||
data = 0;
|
||||
s = 0;
|
||||
}
|
||||
}
|
||||
switch (s) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
data <<= 4;
|
||||
dst[j++] = Base64[((data >> 6) & 0x3f)];
|
||||
dst[j++] = Base64[(data & 0x3f)];
|
||||
dst[j++] = Pad64;
|
||||
dst[j++] = Pad64;
|
||||
break;
|
||||
case 2:
|
||||
data <<= 2;
|
||||
dst[j++] = Base64[((data >> 12) & 0x3f)];
|
||||
dst[j++] = Base64[((data >> 6) & 0x3f)];
|
||||
dst[j++] = Base64[(data & 0x3f)];
|
||||
dst[j++] = Pad64;
|
||||
break;
|
||||
}
|
||||
dst[j] = '\0';
|
||||
return j;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* skips all whitespace anywhere.
|
||||
converts characters, four at a time, starting at (or after)
|
||||
src from base - 64 numbers into three 8 bit bytes in the target area.
|
||||
it returns the number of data bytes stored at the target, or -1 on error.
|
||||
*/
|
||||
int
|
||||
b64_decode(const char *src,
|
||||
char *target,
|
||||
size_t targsize)
|
||||
{
|
||||
int tarindex, state, ch;
|
||||
char *pos;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
state = 0;
|
||||
tarindex = 0;
|
||||
|
||||
while ((ch = *src++) != '\0') {
|
||||
if (isspace(ch)) /* Skip whitespace anywhere. */
|
||||
continue;
|
||||
|
||||
if (ch == Pad64)
|
||||
break;
|
||||
|
||||
pos = strchr(Base64, ch);
|
||||
if (pos == 0) /* A non-base64 character. */
|
||||
return (-1);
|
||||
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (target) {
|
||||
if ((size_t)tarindex >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] = (pos - Base64) << 2;
|
||||
}
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (target) {
|
||||
if ((size_t)tarindex + 1 >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] |= (pos - Base64) >> 4;
|
||||
target[tarindex+1] = ((pos - Base64) & 0x0f)
|
||||
<< 4 ;
|
||||
}
|
||||
tarindex++;
|
||||
state = 2;
|
||||
break;
|
||||
case 2:
|
||||
if (target) {
|
||||
if ((size_t)tarindex + 1 >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] |= (pos - Base64) >> 2;
|
||||
target[tarindex+1] = ((pos - Base64) & 0x03)
|
||||
<< 6;
|
||||
}
|
||||
tarindex++;
|
||||
state = 3;
|
||||
break;
|
||||
case 3:
|
||||
if (target) {
|
||||
if ((size_t)tarindex >= targsize)
|
||||
return (-1);
|
||||
target[tarindex] |= (pos - Base64);
|
||||
}
|
||||
tarindex++;
|
||||
state = 0;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We are done decoding Base-64 chars. Let's see if we ended
|
||||
* on a byte boundary, and/or with erroneous trailing characters.
|
||||
*/
|
||||
|
||||
if (ch == Pad64) { /* We got a pad char. */
|
||||
ch = *src++; /* Skip it, get next. */
|
||||
switch (state) {
|
||||
case 0: /* Invalid = in first position */
|
||||
case 1: /* Invalid = in second position */
|
||||
return (-1);
|
||||
|
||||
case 2: /* Valid, means one byte of info */
|
||||
/* Skip any number of spaces. */
|
||||
for ((void)NULL; ch != '\0'; ch = *src++)
|
||||
if (!isspace(ch))
|
||||
break;
|
||||
/* Make sure there is another trailing = sign. */
|
||||
if (ch != Pad64)
|
||||
return (-1);
|
||||
ch = *src++; /* Skip the = */
|
||||
/* Fall through to "single trailing =" case. */
|
||||
/* FALLTHROUGH */
|
||||
|
||||
case 3: /* Valid, means two bytes of info */
|
||||
/*
|
||||
* We know this char is an =. Is there anything but
|
||||
* whitespace after it?
|
||||
*/
|
||||
for ((void)NULL; ch != '\0'; ch = *src++)
|
||||
if (!isspace(ch))
|
||||
return (-1);
|
||||
|
||||
/*
|
||||
* Now make sure for cases 2 and 3 that the "extra"
|
||||
* bits that slopped past the last full byte were
|
||||
* zeros. If we don't check them, they become a
|
||||
* subliminal channel.
|
||||
*/
|
||||
if (target && target[tarindex] != 0)
|
||||
return (-1);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* We ended by seeing the end of the string. Make sure we
|
||||
* have no partial bytes lying around.
|
||||
*/
|
||||
if (state != 0)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
return (tarindex);
|
||||
}
|
||||
|
||||
/*! Get field from metric yang spec
|
||||
* @param[in] metric Metric
|
||||
* @param[in] fiels Which field in metric (eg type, units)
|
||||
* @param[out] result Allocated string. Free after use
|
||||
*/
|
||||
static int
|
||||
get_metric_spec(char *metric,
|
||||
char *field,
|
||||
char **result)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *resbuf = NULL;
|
||||
char *xmlstr;
|
||||
cxobj *mx;
|
||||
cxobj *dx;
|
||||
cxobj *sx;
|
||||
char *val;
|
||||
|
||||
clicon_debug(1, "%s metric:%s", __FUNCTION__, metric);
|
||||
*result = NULL;
|
||||
if ((resbuf = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
if (netconf_rpc(resbuf,
|
||||
"<rpc><grideye><metric_spec>%s</metric_spec></grideye></rpc>]]>]]>",
|
||||
metric) < 0){
|
||||
clicon_debug(1, "%s error", __FUNCTION__);
|
||||
goto done;
|
||||
}
|
||||
xmlstr = cbuf_get(resbuf);
|
||||
clicon_debug(1, "xmlstr: %s", xmlstr);
|
||||
if (clicon_xml_parse_string(&xmlstr, &mx) < 0){
|
||||
clicon_debug(1, "err:%d reason:%s", clicon_errno, clicon_err_reason);
|
||||
goto done;
|
||||
}
|
||||
if ((dx = xpath_first(mx, "//data")) != NULL){
|
||||
if ((sx = xpath_first(dx, metric)) != NULL){
|
||||
if ((val = xml_find_body(sx, field)) != NULL)
|
||||
*result = strdup(val);
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (resbuf)
|
||||
cbuf_free(resbuf);
|
||||
if (mx)
|
||||
xml_free(mx);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
metric_spec_description(char *metric,
|
||||
char **result)
|
||||
{
|
||||
return get_metric_spec(metric, "description", result);
|
||||
}
|
||||
|
||||
int
|
||||
metric_spec_units(char *metric,
|
||||
char **result)
|
||||
{
|
||||
return get_metric_spec(metric, "units", result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
char *v;
|
||||
int i;
|
||||
cbuf *path = NULL;
|
||||
cbuf *path1 = NULL;
|
||||
cxobj *xt = NULL;
|
||||
cxobj *xg = 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);
|
||||
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)
|
||||
cprintf(cbx, "{\n");
|
||||
if (xml2json_cbuf(cbx, xt, 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, "}");
|
||||
FCGX_FPrintF(r->out, "%s\r\n", cbuf_get(cbx));
|
||||
retval = 0;
|
||||
done:
|
||||
|
|
@ -172,146 +206,139 @@ api_data_get(clicon_handle h,
|
|||
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);
|
||||
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;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
if ((cmd = cbuf_new()) == NULL)
|
||||
goto done;
|
||||
if (pi >= cvec_len(pcvec)){
|
||||
retval = notfound(r);
|
||||
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)
|
||||
goto done;
|
||||
if (cli_cmd(r, "configure", "commit") < 0)
|
||||
goto done;
|
||||
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;
|
||||
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;
|
||||
}
|
||||
clicon_debug_xml(1, "json xml:", xdata);
|
||||
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;
|
||||
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);
|
||||
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){
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 *
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,"",
|
||||
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":"");
|
||||
cprintf(cb, "]%s",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)
|
||||
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;
|
||||
cprintf(cb, "%*s}%s",
|
||||
pretty?(level*INDENT):0,"",
|
||||
pretty?"\n":"");
|
||||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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"},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -268,6 +268,7 @@ 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] api_path restconf api path
|
||||
* @param[in] xml XML data string
|
||||
* @param[in] label Memory chunk label
|
||||
* @retval msg Encoded message
|
||||
|
|
@ -276,6 +277,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)
|
||||
{
|
||||
|
|
@ -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",
|
||||
clicon_debug(2, "%s: op: %d db: %s api_path: %s xml: %s",
|
||||
__FUNCTION__,
|
||||
op, db, xml);
|
||||
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",
|
||||
clicon_debug(2, "%s: op: %d db: %s api_path: %s xml: %s",
|
||||
__FUNCTION__,
|
||||
*op, *db, *xml);
|
||||
*op, *db, *api_path, *xml);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct clicon_msg *
|
||||
clicon_msg_save_encode(char *db, uint32_t snapshot, char *filename,
|
||||
const char *label)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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,7 +552,7 @@ xml_tree_prune_unmarked(cxobj *xt,
|
|||
}
|
||||
|
||||
/*!
|
||||
* @param[in] xkey xmlkey
|
||||
* @param[in] xk xmlkey
|
||||
* @param[out] xt XML tree as result
|
||||
* XXX cannot handle top-level list
|
||||
*/
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue