diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 5dd4ca43..96cf7e49 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -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; diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index f815b42b..1b7888f8 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -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); diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 81fd2d69..70319730 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -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) diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index 39716908..0b50e523 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -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 ... 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", diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 22a670b4..0c7628f6 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -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, "

Grideye Not Found

\n"); + FCGX_FPrintF(r->out, "

Clixon Not Found

\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) { - int retval = -1; - char buf[512]; - int f; - int n; - cbuf *cb = NULL; + 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, "

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; + 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: - cbuf_free(cb); + 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, "
%s
\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. @@ -318,330 +98,73 @@ str2cvec(char *string, char delim2, cvec **cvp) { - int retval = -1; - char *s; - char *s0 = NULL;; - char *val; /* value */ - char *valu; /* unescaped value */ - char *snext; /* next element in string */ - cvec *cvv = NULL; - cg_var *cv; + int retval = -1; + char *s; + char *s0 = NULL;; + char *val; /* value */ + char *valu; /* unescaped value */ + char *snext; /* next element in string */ + cvec *cvv = NULL; + cg_var *cv; - clicon_debug(1, "%s %s", __FUNCTION__, string); - if ((s0 = strdup(string)) == NULL){ - clicon_debug(1, "error strdup %s", strerror(errno)); - goto err; - } - s = s0; - if ((cvv = cvec_new(0)) ==NULL){ - clicon_debug(1, "error cvec_new %s", strerror(errno)); - goto err; - } - while (s != NULL) { - /* - * In the pointer algorithm below: - * name1=val1; name2=val2; - * ^ ^ ^ - * | | | - * s val snext - */ - if ((snext = index(s, delim1)) != NULL) - *(snext++) = '\0'; - if ((val = index(s, delim2)) != NULL){ - *(val++) = '\0'; - if ((valu = curl_easy_unescape(NULL, val, 0, NULL)) == NULL){ - clicon_debug(1, "curl_easy_unescape %s", strerror(errno)); - goto err; - } - if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ - clicon_debug(1, "error cvec_add %s", strerror(errno)); - goto err; - } - cv_name_set(cv, s); - cv_string_set(cv, valu); - free(valu); - } - else{ - if (strlen(s)){ - if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ - clicon_debug(1, "error cvec_add %s", strerror(errno)); - goto err; - } - cv_name_set(cv, s); - cv_string_set(cv, ""); - } - } - s = snext; - } - retval = 0; + clicon_debug(1, "%s %s", __FUNCTION__, string); + if ((s0 = strdup(string)) == NULL){ + clicon_debug(1, "error strdup %s", strerror(errno)); + goto err; + } + s = s0; + if ((cvv = cvec_new(0)) ==NULL){ + clicon_debug(1, "error cvec_new %s", strerror(errno)); + goto err; + } + while (s != NULL) { + /* + * In the pointer algorithm below: + * name1=val1; name2=val2; + * ^ ^ ^ + * | | | + * s val snext + */ + if ((snext = index(s, delim1)) != NULL) + *(snext++) = '\0'; + if ((val = index(s, delim2)) != NULL){ + *(val++) = '\0'; + if ((valu = curl_easy_unescape(NULL, val, 0, NULL)) == NULL){ + clicon_debug(1, "curl_easy_unescape %s", strerror(errno)); + goto err; + } + if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ + clicon_debug(1, "error cvec_add %s", strerror(errno)); + goto err; + } + cv_name_set(cv, s); + cv_string_set(cv, valu); + free(valu); + } + else{ + if (strlen(s)){ + if ((cv = cvec_add(cvv, CGV_STRING)) == NULL){ + clicon_debug(1, "error cvec_add %s", strerror(errno)); + goto err; + } + cv_name_set(cv, s); + cv_string_set(cv, ""); + } + } + s = snext; + } + retval = 0; done: - *cvp = cvv; - if (s0) - free(s0); - return retval; + *cvp = cvv; + if (s0) + free(s0); + return retval; err: - if (cvv){ - cvec_free(cvv); - cvv = NULL; - } - 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: //[=] - * @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, - "]]>]]>", - entry, attr, val) < 0){ - clicon_debug(1, "%s error", __FUNCTION__); - goto done; - } - } - else - if (netconf_rpc(resbuf, - "]]>]]>", - 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) + if (cvv){ cvec_free(cvv); - clicon_debug(1, "%s end %d", __FUNCTION__, retval); - return retval; + cvv = NULL; + } + goto done; } /*! @@ -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, - "%s]]>]]>", - 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); -} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index d465fa9c..b9b1ee08 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -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_ */ diff --git a/apps/restconf/restconf_main.c b/apps/restconf/restconf_main.c index ae485a62..38ac35d7 100644 --- a/apps/restconf/restconf_main.c +++ b/apps/restconf/restconf_main.c @@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License along with CLIXON; see the file LICENSE. If not, see . - + */ /* - * 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] pcvec Vector of path ie DOCUMENT_URI element - * @param[in] pi Offset, where to start pcvec - * @param[in] dvec Stream input data + * @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 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; iys_argument); + goto done; + } + clicon_debug(1, "ykey:%s", ykey->ys_argument); + + /* The value is a list of keys: [ ]* */ + 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; iout, "%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 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= 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 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){ diff --git a/lib/clixon/clixon_proto.h b/lib/clixon/clixon_proto.h index 1c624d63..67fca801 100644 --- a/lib/clixon/clixon_proto.h +++ b/lib/clixon/clixon_proto.h @@ -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: diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index 0665bb9e..faf11f7a 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -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); diff --git a/lib/clixon/clixon_proto_encode.h b/lib/clixon/clixon_proto_encode.h index 497d0a61..5f507cd4 100644 --- a/lib/clixon/clixon_proto_encode.h +++ b/lib/clixon/clixon_proto_encode.h @@ -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 * diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index c3e11e0c..b46abdfb 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -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); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index dbede926..95325a1f 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -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); diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 2c99f622..373ccfa6 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -105,15 +105,14 @@ OK, still something wrong with grafana plots /* cligen */ #include -/* 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 123 -> {"a":[1,2],"b":3} * ie 132 -> {"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 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 */ diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c index 49ff93b3..1bbb13d8 100644 --- a/lib/src/clixon_proto.c +++ b/lib/src/clixon_proto.c @@ -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"}, diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index e9a38684..20167cf0 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -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: ..... * @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; diff --git a/lib/src/clixon_proto_encode.c b/lib/src/clixon_proto_encode.c index 2f35b6ad..9f546c22 100644 --- a/lib/src/clixon_proto_encode.c +++ b/lib/src/clixon_proto_encode.c @@ -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) diff --git a/lib/src/clixon_qdb.c b/lib/src/clixon_qdb.c index 153e9352..6a89a86d 100644 --- a/lib/src/clixon_qdb.c +++ b/lib/src/clixon_qdb.c @@ -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; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 7e807098..21e5802c 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -34,7 +34,7 @@ /* cligen */ #include -/* 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 * diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index a16a5aad..ecd0693b 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -92,13 +92,14 @@ * | 17| <------------- | /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("17", &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 (iys_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: [ ]* */ + 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, diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 66753e19..e84595c2 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -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 : */ - 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