clixon/lib/src/clixon_path.c
Olof hagsand 96ab7292b9 * Added cvv_i output parameter to api_path_fmt2api_path() to see how many cvv entries were used
* CLIspec dbxml API: Ability to specify deletion of _any_ vs _specific_ entry.
2020-12-29 11:14:34 +01:00

1803 lines
52 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2019 Olof Hagsand
Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
This file is part of CLIXON.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 3 or later (the "GPL"),
in which case the provisions of the GPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of the GPL, and not to allow others to
use your version of this file under the terms of Apache License version 2,
indicate your decision by deleting the provisions above and replace them with
the notice and other provisions required by the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the Apache License version 2 or the GPL.
***** END LICENSE BLOCK *****
* This file has code for several variants of paths in cxobj trees:
* - api-path as defined by RESTCONF
* - instance-identifier as defined by YANG
* - clixon-path is an internal format which both ^ use as internal representation
*
* 1. Instance-identifier
* "Instance-identifier" is a subset of XML Xpaths and defined in Yang, used in NACM for example.
* and defined in RF7950 Sections 9.13 and 14.
* To note: prefixes depend on the XML context in which the value occurs,
* In RFC8341 node-instance-identifiers are defined as:
* All the same rules as an instance-identifier apply, except that predicates for keys are optional.
* If a key predicate is missing, then the node-instance-identifier represents all possible server
* instances for that key.
*
* 2. Api-path
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* BNF:
* <api-path> := <root> ("/" (<api-identifier> | <list-instance>))*
* <root> := <string> # See note 1 below
* <api-identifier> := [<module-name> ":"] <identifier>
* <module-name> := <identifier>
* <list-instance> := <api-identifier> "=" key-value *("," key-value)
* <key-value> := <string>
* <string> := <an unquoted string>
* <identifier> := (<ALPHA> | "_") (<ALPHA> | <DIGIT> | "_" | "-" | ".")
*
* @note 1. <root> is the RESTCONF root resource (Sec 3.3) omitted in all calls below, it is
* assumed to be stripped from api-path before calling these functions.
* @note 2. characters in a key value string are constrained, and some characters need to be
* percent-encoded,
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <assert.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_hash.h"
#include "clixon_handle.h"
#include "clixon_string.h"
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_options.h"
#include "clixon_yang.h"
#include "clixon_xml.h"
#include "clixon_xml_nsctx.h"
#include "clixon_xml_vec.h"
#include "clixon_xml_sort.h"
#include "clixon_netconf_lib.h"
#include "clixon_xml_map.h"
#include "clixon_yang_module.h"
#include "clixon_path.h"
#include "clixon_api_path_parse.h"
#include "clixon_instance_id_parse.h"
/*! Given api-path, parse it, and return a clixon-path struct
*
* @param[in] api_path String with api-path syntax according to RESTCONF RFC8040
* @param[out] cplist Structured internal clixon-path
* @retval 0 OK
* @retval -1 Error
* @code
* clixon_path *cplist = NULL;
* if (api_path_parse(api_path, &cplist) < 0)
* err;
* if (api_path_resolve(cplist, yt) < 0)
* err;
* ...
* if (cplist)
* clixon_path_free(cplist);
* @endcode
* @see clixon_path_free
*/
static int
api_path_parse(char *api_path,
clixon_path **cplist)
{
int retval = -1;
clixon_api_path_yacc ay = {0,};
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
ay.ay_parse_string = api_path;
ay.ay_name = "api-path parser";
ay.ay_linenum = 1;
if (api_path_scan_init(&ay) < 0)
goto done;
if (api_path_parse_init(&ay) < 0)
goto done;
if (clixon_api_path_parseparse(&ay) != 0) { /* yacc returns 1 on error */
clicon_log(LOG_NOTICE, "API-PATH error: on line %d", ay.ay_linenum);
if (clicon_errno == 0)
clicon_err(OE_XML, 0, "API-PATH parser error with no error code (should not happen)");
goto done;
}
api_path_parse_exit(&ay);
api_path_scan_exit(&ay);
*cplist = ay.ay_top;
retval = 0;
done:
return retval;
}
/*! Given instance-id path, parse it, and return an clixon-path struct
*
* @param[in] api_path String with syntax according to YANG RFC7980
* @param[out] cplist Structured internal clixon-path
* @retval 0 OK
* @retval -1 Error
* @code
* clixon_path *cplist = NULL;
* if (instance_id_parse(api_path, &cplist) < 0)
* err;
* ...
* if (cplist)
* clixon_path_free(cplist);
* @endcode
* @see clixon_path_free
*/
static int
instance_id_parse(char *path,
clixon_path **cplist)
{
int retval = -1;
clixon_instance_id_yacc iy = {0,};
clicon_debug(1, "%s path:%s", __FUNCTION__, path);
iy.iy_parse_string = path;
iy.iy_name = "instance-id parser";
iy.iy_linenum = 1;
if (instance_id_scan_init(&iy) < 0)
goto done;
if (instance_id_parse_init(&iy) < 0)
goto done;
if (clixon_instance_id_parseparse(&iy) != 0) { /* yacc returns 1 on error */
clicon_log(LOG_NOTICE, "Instance-id error: on line %d", iy.iy_linenum);
if (clicon_errno == 0)
clicon_err(OE_XML, 0, "Instance-id parser error with no error code (should not happen)");
goto done;
}
instance_id_parse_exit(&iy);
instance_id_scan_exit(&iy);
*cplist = iy.iy_top;
retval = 0;
done:
return retval;
}
static int
clixon_path_free(clixon_path *cplist)
{
clixon_path *cp;
while ((cp = cplist) != NULL){
DELQ(cp, cplist, clixon_path *);
if (cp->cp_prefix)
free(cp->cp_prefix);
if (cp->cp_id)
free(cp->cp_id);
if (cp->cp_cvk)
cvec_free(cp->cp_cvk);
free(cp);
}
return 0;
}
/*! Print path on instance-id/xpath form
*/
static int
clixon_path_print(FILE *f,
clixon_path *cplist)
{
clixon_path *cp;
cg_var *cv;
if ((cp = cplist) != NULL){
do {
fprintf(f, "/");
if (cp->cp_prefix)
fprintf(f, "%s:", cp->cp_prefix);
fprintf(f, "%s", cp->cp_id);
if (cp->cp_cvk){
fprintf(f, "=");
cv = NULL;
while ((cv = cvec_each(cp->cp_cvk, cv)) != NULL){
fprintf(f, "[");
/* If cvk has one integer argument, interpret as position, eg x/y[42] */
if (cvec_len(cp->cp_cvk) == 1 && (cv_type_get(cv) == CGV_UINT32))
fprintf(f, "%u", cv_uint32_get(cv));
else
fprintf(f, "%s=\"%s\"", cv_name_get(cv), cv_string_get(cv));
fprintf(f, "]");
}
}
cp = NEXTQ(clixon_path *, cp);
} while (cp && cp != cplist);
}
fprintf(f, "\n");
return 0;
}
/*! Given an XML node, return root node
* A root node is an ancestor xr of x with one or both of the following properties
* - its XML parent is NULL parent,
* - its associated yang specification's parent is a yang module.
* @param[in] x XML node
* @param[out] xr XML root
*/
int
xml_yang_root(cxobj *x,
cxobj **xr)
{
int retval = -1;
cxobj *xp;
yang_stmt *y;
yang_stmt *yp;
while ((xp = xml_parent(x)) != NULL){
if ((y = xml_spec(x)) != NULL &&
(yp = yang_parent_get(y)) != NULL)
/* Actually, maybe only the Y_MODULE clause is relevant */
if (yp==NULL ||
yang_keyword_get(yp) == Y_MODULE ||
yang_keyword_get(yp) == Y_SUBMODULE)
break; /* x is the root */
x = xp;
}
*xr = x;
retval = 0;
return retval;
}
/*! Construct an api-path key format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* path: /modname:a/b/%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] cb api_path_fmt,
* @retval 0 OK
* @retval -1 Error
* @see RFC8040 3.5.3 where "api-path" is defined as "URI-encoded path expression"
*/
static int
yang2api_path_fmt_1(yang_stmt *ys,
int inclkey,
cbuf *cb)
{
yang_stmt *yp; /* parent */
yang_stmt *ymod = NULL;
yang_stmt *ypmod = NULL;
int i;
cvec *cvk = NULL; /* vector of index keys */
int retval = -1;
if ((yp = yang_parent_get(ys)) == NULL){
clicon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
goto done;
}
if (yp != NULL && /* XXX rm */
yang_keyword_get(yp) != Y_MODULE &&
yang_keyword_get(yp) != Y_SUBMODULE){
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
goto done;
if (yang_keyword_get(yp) != Y_CHOICE && yang_keyword_get(yp) != Y_CASE){
#if 0
/* In some cases, such as cli_show_auto, a trailing '/' should
* NOT be present if ys is a key in a list.
* But in other cases (I think most), the / should be there,
* so a patch is added in cli_show_auto instead.
*/
if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST &&
yang_key_match(yp, ys->ys_argument) == 1)
;
else
#endif
cprintf(cb, "/");
}
/* If parent namespace/module is different from child -> add child prefix */
if (ys_real_module(yp, &ypmod) < 0)
goto done;
if (ys_real_module(ys, &ymod) < 0)
goto done;
if (ypmod != ymod)
cprintf(cb, "%s:", yang_argument_get(ymod));
}
else /* top symbol - mark with name prefix */
cprintf(cb, "/%s:", yang_argument_get(yp));
if (inclkey){
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
cprintf(cb, "%s", yang_argument_get(ys));
}
else{
if (yang_keyword_get(ys) == Y_LEAF && yp &&
yang_keyword_get(yp) == Y_LIST){
if (yang_key_match(yp, yang_argument_get(ys)) == 0)
cprintf(cb, "%s", yang_argument_get(ys)); /* Not if leaf and key */
}
else
if (yang_keyword_get(ys) != Y_CHOICE && yang_keyword_get(ys) != Y_CASE)
cprintf(cb, "%s", yang_argument_get(ys));
}
switch (yang_keyword_get(ys)){
case Y_LIST:
cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
for (i=0; i<cvec_len(cvk); i++){
if (i)
cprintf(cb, ",");
cprintf(cb, "%%s");
}
break;
case Y_LEAF_LIST:
cprintf(cb, "=%%s");
break;
default:
break;
} /* switch */
retval = 0;
done:
return retval;
}
/*! Construct an api_path_format from yang statement using wildcards for keys
* Recursively construct it to the top.
* Example:
* yang: container a -> list b -> key c -> leaf d
* api_path: /a/b=%s/d
* @param[in] ys Yang statement
* @param[in] inclkey If set include key leaf (eg last leaf d in ex)
* @param[out] api_path_fmt XML api path. Needs to be freed after use.
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
int
yang2api_path_fmt(yang_stmt *ys,
int inclkey,
char **api_path_fmt)
{
int retval = -1;
cbuf *cb = NULL;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (yang2api_path_fmt_1(ys, inclkey, cb) < 0)
goto done;
if ((*api_path_fmt = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Transform an xml key format and a vector of values to an XML key
* Used for actual key, eg in clicon_rpc_change(), xmldb_put_xkey()
* Example:
* xmlkeyfmt: /interfaces/interface=%s/ipv4/address=%s
* cvv: 0 : set interfaces interface e ipv4 address 1.2.3.4
* 1 : name = "e"
* 2 : ip = "1.2.3.4"
* api_path: /interfaces/interface=e/ipv4/address=1.2.3.4
* @param[in] api_path_fmt XML key format, eg /aaa/%s/name
* @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] api_path api_path, eg /aaa/17. Free after use
* @param[out] cvvi 1..cvv-len. Index into cvv of last cvv entry used, For example,
* if same as len of cvv, all were used, if < some entries were not
* @retval 0 OK
* @retval -1 Error
* @note first and last elements of cvv are not used,..
* @see api_path_fmt2xpath
* @example
* api_path_fmt: /interfaces/interface=%s/name
* cvv: -
* api_path: /interfaces/interface/name
* @example
* api_path_fmt: /interfaces/interface=%s/name
* cvv: e0
* api_path: /interfaces/interface=e0/name
* @example
* api_path_fmt: /subif-entry=%s,%s/subid
* cvv: foo, bar
* api_path: /subif-entry=foo,bar/subid
*
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
int
api_path_fmt2api_path(const char *api_path_fmt,
cvec *cvv,
char **api_path,
int *cvv_i)
{
int retval = -1;
char c;
int esc=0;
cbuf *cb = NULL;
int i;
int j;
char *str;
char *strenc=NULL;
cg_var *cv;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(api_path_fmt); i++){
c = api_path_fmt[i];
if (esc){
esc = 0;
if (c!='s')
continue;
if (j == cvec_len(cvv)) /* last element */
;
else{
cv = cvec_i(cvv, j++);
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
if (uri_percent_encode(&strenc, "%s", str) < 0)
goto done;
cprintf(cb, "%s", strenc);
free(strenc); strenc = NULL;
free(str); str = NULL;
}
}
else
if (c == '%')
esc++;
else{
if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%' && j == cvec_len(cvv))
; /* skip */
else
cprintf(cb, "%c", c);
}
}
if ((*api_path = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
if (cvv_i) /* Last entry in cvv used */
*cvv_i = j;
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Transform an xml key format and a vector of values to an XML path
* Used to input xmldb_get()
* @param[in] api_path_fmt XML key format
* @param[in] cvv cligen variable vector, one for every wildchar in api_path_fmt
* @param[out] xpath XPATH
* Add .* in last %s position.
* @example
* api_path_fmt: /interface/%s/address/%s
* cvv: name=eth0
* xpath: /interface/[name='eth0']/address
* @example
* api_path_fmt: /ip/me/%s (if key)
* cvv: -
* xpath: /ipv4/me/a
* @example
* api_path_fmt: /subif-entry=%s,%s/subid
* cvv: foo
* xpath: /subif-entry[if-name=foo]/subid"
* @example
* api_path_fmt: /a:b/c
* xpath : /b/c prefix:a
* "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
*/
int
api_path_fmt2xpath(char *api_path_fmt,
cvec *cvv,
char **xpath)
{
int retval = -1;
char c;
int esc=0;
cbuf *cb = NULL;
int i;
int j;
char *str;
cg_var *cv;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
j = 1; /* j==0 is cli string */
for (i=0; i<strlen(api_path_fmt); i++){
c = api_path_fmt[i];
if (esc){
esc = 0;
if (c!='s')
continue;
if (j == cvec_len(cvv)) /* last element */
;
else{
cv = cvec_i(cvv, j++);
if ((str = cv2str_dup(cv)) == NULL){
clicon_err(OE_UNIX, errno, "cv2str_dup");
goto done;
}
cprintf(cb, "[%s='%s']", cv_name_get(cv), str);
free(str);
}
}
else /* regular char */
if (c == '%')
esc++;
else{
if ((c == '=' || c == ',') && api_path_fmt[i+1]=='%')
; /* skip */
else
cprintf(cb, "%c", c);
}
}
if ((*xpath = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
/*! Translate from restconf api-path(cvv) to xml xpath(cbuf) and namespace context
*
* @param[in] api_path URI-encoded path expression" (RFC8040 3.5.3) as cvec
* @param[in] offset Offset of cvec, where api-path starts
* @param[in] yspec Yang spec
* @param[in,out] xpath The xpath as cbuf (must be created and may have content)
* @param[out] nsc Namespace context of xpath (free w xml_nsctx_free)
* @param[out] xerr Netconf error message
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error xml set
* @retval -1 Fatal error, clicon_err called
*
* @code
* cbuf *xpath = cbuf_new();
* cvec *cvv = NULL;
* cvec *nsc = NULL;
* if (str2cvec("www.foo.com/restconf/a/b=c", '/', '=', &cvv) < 0)
* err;
* if ((ret = api_path2xpath_cvv(yspec, cvv, 0, cxpath, &nsc, NULL)) < 0)
* err;
* if (ret == 1)
* ... access xpath as cbuf_get(xpath)
* cbuf_free(xpath);
* cvec_free(nsc);
* @endcode
* It works like this:
* Assume origin incoming path is
* "www.foo.com/restconf/a/b=c", pi is 2 and pcvec is:
* ["www.foo.com" "restconf" "a" "b=c"]
* which means the api-path is ["a" "b=c"] corresponding to "a/b=c"
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @note retval -1 sets clicon_err, retval 0 sets netconf xml msg
* @note Not proper namespace translation from api-path 2 xpath!!!
* @see api_path2xml For api-path to xml tree
* @see api_path2xpath Using strings as parameters
*/
static int
api_path2xpath_cvv(cvec *api_path,
int offset,
yang_stmt *yspec,
cbuf *xpath,
cvec **nscp,
cxobj **xerr)
{
int retval = -1;
int i;
cg_var *cv;
char *nodeid;
char *prefix = NULL; /* api-path (module) prefix */
char *xprefix = NULL; /* xml xpath prefix */
char *name = NULL;
cvec *cvk = NULL; /* vector of index keys */
yang_stmt *y = NULL;
yang_stmt *ymod = NULL;
char *val;
cg_var *cvi;
char **valvec = NULL;
int vi;
int nvalvec;
cbuf *cberr = NULL;
char *namespace = NULL;
cvec *nsc = NULL;
cprintf(xpath, "/");
/* Initialize namespace context */
if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL)
goto done;
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
for (i=offset; i<cvec_len(api_path); i++){
cv = cvec_i(api_path, i);
nodeid = cv_name_get(cv);
/* api-path: prefix points to module */
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
clicon_debug(1, "%s [%d] cvname: %s:%s",
__FUNCTION__, i, prefix?prefix:"", name);
/* top-node must have prefix */
if (i == offset && prefix == NULL){
cprintf(cberr, "'%s': Expected prefix:name", nodeid);
if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
ymod = NULL;
if (prefix){ /* if prefix -> get module + change namespace */
if ((ymod = yang_find_module_by_name(yspec, prefix)) == NULL){
cprintf(cberr, "No such yang module: %s", prefix);
if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod); /* change namespace */
}
if (i == offset && ymod) /* root */
y = yang_find_datanode(ymod, name);
else
y = yang_find_datanode(y, name);
if (y == NULL){
if (xerr && netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
goto done;
goto fail;
}
/* Get XML/xpath prefix given namespace.
* note different from api-path prefix
*/
if (xml_nsctx_get_prefix(nsc, namespace, &xprefix) == 0){
xprefix = yang_find_myprefix(y);
clicon_debug(1, "%s prefix not found add it %s", __FUNCTION__, xprefix);
/* not found, add it to nsc
* XXX: do we always have to add it? It could be default?
*/
// if (xml2prefix(x1, namespace, &pexisting));
if (xml_nsctx_add(nsc, xprefix, namespace) < 0)
goto done;
}
/* Check if has value, means '=' */
if (cv2str(cv, NULL, 0) > 0){
if ((val = cv2str_dup(cv)) == NULL)
goto done;
switch (yang_keyword_get(y)){
case Y_LIST:
/* Transform value "a,b,c" to "a" "b" "c" (nvalvec=3)
* Note that vnr can be < length of cvk, due to empty or unset values
*/
if (valvec){ /* loop, valvec may have been used before */
free(valvec);
valvec = NULL;
}
if ((valvec = clicon_strsep(val, ",", &nvalvec)) == NULL)
goto done;
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
cvi = NULL;
/* Iterate over individual yang keys */
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
vi = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL && vi<nvalvec){
cprintf(xpath, "[");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s='%s']", cv_string_get(cvi), valvec[vi++]);
}
break;
case Y_LEAF_LIST: /* XXX: LOOP? */
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
if (val)
cprintf(xpath, "[.='%s']", val);
else
cprintf(xpath, "[.='']");
break;
default:
if (i != offset)
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
break;
}
if (val)
free(val);
}
else{
if (i != offset)
cprintf(xpath, "/");
if (xprefix)
cprintf(xpath, "%s:", xprefix);
cprintf(xpath, "%s", name);
}
if (prefix){
free(prefix);
prefix = NULL;
}
if (name){
free(name);
name = NULL;
}
} /* for */
retval = 1; /* OK */
if (nscp){
*nscp = nsc;
nsc = NULL;
}
done:
clicon_debug(2, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
if (valvec)
free(valvec);
if (prefix)
free(prefix);
if (nsc)
cvec_free(nsc);
if (name)
free(name);
return retval;
fail:
retval = 0; /* Validation failed */
goto done;
}
/*! Translate from restconf api-path to xml xpath and namespace
* @param[in] api_path URI-encoded path expression" (RFC8040 3.5.3)
* @param[in] yspec Yang spec
* @param[out] xpath xpath (use free() to deallocate)
* @param[out] nsc Namespace context of xpath (free w xml_nsctx_free)
* @param[out] xerr Netconf error message
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf called
* @retval -1 Fatal error, clicon_err called
* @code
* char *xpath = NULL;
* cvec *nsc = NULL;
* if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc)) < 0)
* err;
* if (ret == 1)
* ... access xpath as cbuf_get(xpath)
* free(xpath)
* cvec_free(nsc);
* @endcode
* @see api_path2xml For api-path to xml translation (maybe could be combined?)
*/
int
api_path2xpath(char *api_path,
yang_stmt *yspec,
char **xpathp,
cvec **nsc,
cxobj **xerr)
{
int retval = -1;
cvec *cvv = NULL; /* api-path vector */
cbuf *xpath = NULL; /* xpath as cbuf (sub-function uses that) */
int ret;
if (api_path == NULL){
clicon_err(OE_XML, EINVAL, "api_path is NULL");
goto done;
}
/* Split api-path into cligen variable vector */
if (str2cvec(api_path, '/', '=', &cvv) < 0)
goto done;
if ((xpath = cbuf_new()) == NULL)
goto done;
if ((ret = api_path2xpath_cvv(cvv, 0, yspec, xpath, nsc, xerr)) < 0)
goto done;
if (ret == 0)
goto fail;
/* prepare output xpath parameter */
if (xpathp)
if ((*xpathp = strdup(cbuf_get(xpath))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
retval = 1;
done:
if (cvv)
cvec_free(cvv);
if (xpath)
cbuf_free(xpath);
return retval;
fail:
retval = 0; /* Validation failed */
goto done;
}
/*! Create xml tree from api-path as vector
* @param[in] vec APIpath as char* vector
* @param[in] nvec Length of vec
* @param[in] x0 Xpath tree so far
* @param[in] y0 Yang spec for x0
* @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xbotp Resulting xml tree
* @param[out] ybotp Yang spec matching xpathp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error, clicon_err called
*
* @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
* @see api_path2xpath For api-path to xml xpath translation
* @see api_path2xml
*/
static int
api_path2xml_vec(char **vec,
int nvec,
cxobj *x0,
yang_stmt *y0,
yang_class nodeclass,
int strict,
cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
{
int retval = -1;
char *nodeid;
char *name = NULL;
char *prefix = NULL;
char *restval = NULL;
char *restval_enc;
cxobj *xn = NULL; /* new */
cxobj *xb; /* body */
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
char *keyname;
char **valvec = NULL;
int nvalvec = 0;
int vi;
cxobj *x = NULL;
yang_stmt *y = NULL;
yang_stmt *ymod;
yang_stmt *ykey;
char *namespace = NULL;
cbuf *cberr = NULL;
if ((nodeid = vec[0]) == NULL || strlen(nodeid)==0){
if (xbotp)
*xbotp = x0;
if (ybotp)
*ybotp = y0;
goto ok;
} /* E.g "x=1,2" -> nodeid:x restval=1,2 */
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
/* restval is RFC 3896 encoded */
if ((restval_enc = index(nodeid, '=')) != NULL){
*restval_enc = '\0';
restval_enc++;
if (uri_percent_decode(restval_enc, &restval) < 0)
goto done;
}
/* Split into prefix and localname */
if (nodeid_split(nodeid, &prefix, &name) < 0)
goto done;
if (yang_keyword_get(y0) == Y_SPEC){ /* top-node */
if (prefix == NULL){
cprintf(cberr, "api-path element '%s', expected prefix:name", nodeid);
if (xerr &&
netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if ((ymod = yang_find_module_by_name(y0, prefix)) == NULL){
cprintf(cberr, "No such yang module prefix");
if (xerr &&
netconf_unknown_element_xml(xerr, "application", prefix, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod);
y0 = ymod;
}
y = (nodeclass==YC_SCHEMANODE)?
yang_find_schemanode(y0, name):
yang_find_datanode(y0, name);
if (y == NULL){
if (xerr &&
netconf_unknown_element_xml(xerr, "application", name, "Unknown element") < 0)
goto done;
goto fail;
}
if (prefix && namespace == NULL){
if ((ymod = yang_find_module_by_name(ys_spec(y0), prefix)) == NULL){
cprintf(cberr, "api-path element prefix: '%s', no such yang module", prefix);
if (xerr &&
netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
namespace = yang_find_mynamespace(ymod);
}
switch (yang_keyword_get(y)){
case Y_LEAF_LIST:
#if 0
if (restval==NULL){
clicon_err(OE_XML, 0, "malformed key, expected '=restval'");
goto done;
}
#endif
if (x0 == NULL)
break;
if ((x = xml_new(yang_argument_get(y), x0, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(x, y);
if ((xb = xml_new("body", x, CX_BODY)) == NULL)
goto done;
if (restval && xml_value_set(xb, restval) < 0)
goto done;
break;
case Y_LIST:
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
if (valvec){ /* loop, valvec may have been used before */
free(valvec);
valvec = NULL;
}
if (restval==NULL){
if (strict){
cprintf(cberr, "malformed key =%s, expected '=restval'", nodeid);
if (xerr &&
netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
}
else{
/* Transform restval "a,b,c" to "a" "b" "c" (nvalvec=3)
* Note that vnr can be < length of cvk, due to empty or unset values
*/
if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL)
goto done;
if ((nvalvec != cvec_len(cvk)) && strict){
cprintf(cberr, "List key %s length mismatch", name);
if (xerr &&
netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
}
cvi = NULL;
/* create list object */
if (x0){
if ((x = xml_new(name, x0, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(x, y);
}
vi = 0;
/* Create keys */
while ((cvi = cvec_each(cvk, cvi)) != NULL){
keyname = cv_string_get(cvi);
if ((ykey = yang_find(y, Y_LEAF, keyname)) == NULL){
cprintf(cberr, "List statement \"%s\" has no key leaf \"%s\"",
yang_argument_get(y), keyname);
if (xerr &&
netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if (x != NULL){
if ((xn = xml_new(keyname, x, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(xn, ykey);
if ((xb = xml_new("body", xn, CX_BODY)) == NULL)
goto done;
if (vi++ < nvalvec){
if (xml_value_set(xb, valvec[vi-1]) < 0)
goto done;
}
}
}
break;
default: /* eg Y_CONTAINER, Y_LEAF */
if (x0 &&
(x = xml_find_type(x0, NULL, name, CX_ELMNT)) == NULL){ /* eg key of list */
if ((x = xml_new(name, x0, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(x, y);
}
break;
}
if (x && namespace){
if (xmlns_set(x, NULL, namespace) < 0)
goto done;
}
if ((retval = api_path2xml_vec(vec+1, nvec-1,
x, y,
nodeclass, strict,
xbotp, ybotp, xerr)) < 1)
goto done;
ok:
retval = 1; /* OK */
done:
clicon_debug(2, "%s retval:%d", __FUNCTION__, retval);
if (cberr)
cbuf_free(cberr);
if (prefix)
free(prefix);
if (name)
free(name);
if (restval)
free(restval);
if (valvec)
free(valvec);
return retval;
fail:
retval = 0; /* invalid api-path or XML */
goto done;
}
/*! Create xml tree from api-path
* @param[in] api_path (Absolute) API-path as defined in RFC 8040
* @param[in] yspec Yang spec
* @param[in,out] xtop Incoming XML tree
* @param[in] nodeclass Set to schema nodes, data nodes, etc
* @param[out] xbotp Resulting xml tree (end of xpath)
* @param[out] ybotp Yang spec matching xbotp
* @param[out] xerr Netconf error message (if retval=0)
* @retval 1 OK
* @retval 0 Invalid api_path or associated XML, netconf error
* @retval -1 Fatal error, clicon_err called
* @note both retval -1 set clicon_err, retval 0 set xerr netconf xml
* @example
* api_path: /subif-entry=foo/subid
* xtop[in] <config/>
* xtop[out]:<config/> <subif-entry>
* <if-name>foo<if-name><subid/>>
* </subif-entry></config>
* xbotp: <subid/>
* ybotp: Y_LEAF subid
* @code
* if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, &xbot, &y, &xerr)) < 0)
* err;
* @endcode
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @see api_path2xpath For api-path to xpath translation (maybe could be combined?)
*/
int
api_path2xml(char *api_path,
yang_stmt *yspec,
cxobj *xtop,
yang_class nodeclass,
int strict,
cxobj **xbotp,
yang_stmt **ybotp,
cxobj **xerr)
{
int retval = -1;
char **vec = NULL;
int nvec;
cxobj *xroot;
cbuf *cberr = NULL;
clicon_debug(2, "%s api_path:%s", __FUNCTION__, api_path);
if ((cberr = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (*api_path != '/'){
cprintf(cberr, "Invalid api-path: %s (must start with '/')", api_path);
if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
if ((vec = clicon_strsep(api_path, "/", &nvec)) == NULL)
goto done;
/* Remove trailing '/'. Like in /a/ -> /a */
if (nvec > 1 && !strlen(vec[nvec-1]))
nvec--;
if (nvec < 1){
cprintf(cberr, "Malformed api-path: %s: too short)", api_path);
if (xerr && netconf_invalid_value_xml(xerr, "application", cbuf_get(cberr)) < 0)
goto done;
goto fail;
}
nvec--; /* NULL-terminated */
if ((retval = api_path2xml_vec(vec+1, nvec,
xtop, yspec, nodeclass, strict,
xbotp, ybotp, xerr)) < 1)
goto done;
/* Fix namespace */
if (xbotp){
xml_yang_root(*xbotp, &xroot);
if (xmlns_assign(xroot) < 0)
goto done;
}
// ok:
retval = 1;
done:
if (cberr)
cbuf_free(cberr);
if (vec)
free(vec);
return retval;
fail:
retval = 0;
goto done;
}
/*! Construct an api_path from an XML node (single level not recursive)
* @param[in] x XML node (need to be yang populated)
* @param[out] cb api_path, must be initialized
* @retval 0 OK
* @retval -1 Error
* @see yang2api_path_fmt
* @see xml2xpath
*/
int
xml2api_path_1(cxobj *x,
cbuf *cb)
{
int retval = -1;
yang_stmt *y = NULL;
cvec *cvk = NULL; /* vector of index keys */
cg_var *cvi;
enum rfc_6020 keyword;
int i;
char *keyname;
cxobj *xkey;
cxobj *xb;
char *b;
char *enc;
yang_stmt *ymod;
cxobj *xp;
if ((y = xml_spec(x)) == NULL){
cprintf(cb, "/%s", xml_name(x));
goto ok;
}
ymod = ys_module(y);
xp = xml_parent(x);
if (ymod && xp && xml_spec(xp)==NULL) /* Add prefix only if root */
cprintf(cb, "/%s:%s", yang_argument_get(ymod), xml_name(x));
else
cprintf(cb, "/%s", xml_name(x));
keyword = yang_keyword_get(y);
switch (keyword){
case Y_LEAF_LIST:
b = xml_body(x);
enc = NULL;
if (uri_percent_encode(&enc, "%s", b) < 0)
goto done;
cprintf(cb, "=%s", enc?enc:"");
if (enc)
free(enc);
break;
case Y_LIST:
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
if (cvec_len(cvk))
cprintf(cb, "=");
/* Iterate over individual keys */
cvi = NULL;
i = 0;
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
keyname = cv_string_get(cvi);
if ((xkey = xml_find(x, keyname)) == NULL)
goto done; /* No key in xml */
if ((xb = xml_find(x, keyname)) == NULL)
goto done;
if (i++)
cprintf(cb, ",");
b = xml_body(xb);
enc = NULL;
if (uri_percent_encode(&enc, "%s", b) < 0)
goto done;
cprintf(cb, "%s", enc?enc:"");
if (enc)
free(enc);
}
break;
default:
break;
}
#if 0
{ /* Just for testing */
cxobj *xc;
if ((xc = xml_child_i_type(x, 0, CX_ELMNT)) != NULL)
if (xml2api_path_1(xc, cb) < 0)
goto done;
}
#endif
ok:
retval = 0;
done:
return retval;
}
/*! Resolve api-path module:names to yang statements
* @param[in] cplist Lisp of clixon-path
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @retval -1 Error
* @retval 0 Fail
* @retval 1 OK
* Reasons for fail (retval = 0) are: XXX
* - Modulename in api-path does not correspond to existing module
* - Modulename not defined for top-level id.
* - Corresponding yang node for id not found
* - Number of keys in key-value list does not match Yang list
* - Key-values only defined for list or leaf-list
* @see instance_id_resolve
*/
static int
api_path_resolve(clixon_path *cplist,
yang_stmt *yt)
{
int retval = -1;
clixon_path *cp;
yang_stmt *yc;
int i;
cg_var *cva;
cg_var *cvy;
if ((cp = cplist) != NULL){
do {
if (yang_keyword_get(yt) == Y_SPEC){
if (cp->cp_prefix == NULL){
clicon_err(OE_YANG, ENOENT, "Modulename not defined for top-level id.");
goto fail;
}
if ((yt = yang_find_module_by_name(yt, cp->cp_prefix)) == NULL){
clicon_err(OE_YANG, ENOENT, "Modulename in api-path does not correspond to existing module");
goto fail;
}
}
if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
clicon_err(OE_YANG, ENOENT, "Corresponding yang node for id not found");
goto fail;
}
cp->cp_yang = yc;
if (cp->cp_cvk){
/* Iterate over yang list keys and assign as names (or "." for leaf-list) in cvk */
if (yang_keyword_get(yc) == Y_LEAF_LIST){
cva = NULL;
while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
if (cv_name_get(cva) == NULL)
cv_name_set(cva, ".");
}
}
else if (yang_keyword_get(yc) == Y_LIST){
if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list");
goto fail;
}
i = 0;
cva = NULL;
while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
if (cv_name_get(cva) != NULL){
clicon_err(OE_YANG, ENOENT, "Unexpected named key %s (shouldnt happen)",
cv_name_get(cva));
goto fail;
}
cvy = cvec_i(yang_cvec_get(yc), i++);
cv_name_set(cva, cv_string_get(cvy));
}
}
else{
clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list");
goto fail;
}
}
yt = yc;
cp = NEXTQ(clixon_path *, cp);
} while (cp && cp != cplist);
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Resolve instance-id prefix:names to yang statements
* @param[in] cplist Lisp of clixon-path
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[in] xt XML statement for prefix context
* @retval -1 Error
* @retval 0 Fail
* @retval 1 OK
* @note: The spec says: prefixes depend on the XML context in which the value occurs.
* However, canonical prefixes/namespaces are used based on loaded yang modules.
* This means that prefix=NULL is not allowed.
* Reasons for fail (retval = 0) are:
* - No prefix of identifier (keynames may omit prefix)
* - Prefix does not correspond to existing module.
* - Corresponding yang node for id not found
* - Number of keys in key-value list does not match Yang list
* - Key-values only defined for list or leaf-list
* @note key-value presence in lists is not enforced due to use-cases where instance-id need not have
* them (eg RFC8341) alternative could be to have a "strict" parameter, but gets complicated
* @see api_path_resolve
*/
static int
instance_id_resolve(clixon_path *cplist,
yang_stmt *yt)
{
int retval = -1;
clixon_path *cp;
yang_stmt *yc;
cg_var *cva;
yang_stmt *yspec;
char *kname;
yspec = ys_spec(yt);
if ((cp = cplist) != NULL){
do {
if (cp->cp_prefix == NULL){
clicon_err(OE_YANG, ENOENT, "No prefix of identifier (keynames may omit prefix)");
goto fail;
}
if (yang_keyword_get(yt) == Y_SPEC){
if ((yt = yang_find_module_by_prefix_yspec(yspec, cp->cp_prefix)) == NULL){
clicon_err(OE_YANG, ENOENT, "Prefix does not correspond to existing module.");
goto fail;
}
}
if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
clicon_err(OE_YANG, ENOENT, "Node %s used in path has no corresponding yang node",
cp->cp_id);
goto fail;
}
cp->cp_yang = yc;
switch (yang_keyword_get(yc)){
case Y_LIST:
if (cp->cp_cvk == NULL){
#if 0 /* see key-value presence in lists note above */
clicon_err(OE_YANG, ENOENT, "key-values mandatory for lists");
goto fail;
#endif
break;
}
#if 0 /* see key-value presence in lists note above */
if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list ");
goto fail;
}
#endif
cva = NULL;
while ((cva = cvec_each(cp->cp_cvk, cva)) != NULL) {
if ((kname = cv_name_get(cva)) != NULL){ /* Check var exists */
if (yang_find(yc, 0, kname) == NULL){
clicon_err(OE_YANG, ENOENT, "Index variable %s used in path is not child of Yang node %s",
kname, yang_argument_get(yc));
goto fail;
}
}
else{ /* Assume index */
}
}
break;
case Y_LEAF_LIST:
break;
default:
if (cp->cp_cvk != NULL){
clicon_err(OE_YANG, ENOENT, "key-values only defined for list or leaf-list");
goto fail;
}
break;
}
yt = yc;
cp = NEXTQ(clixon_path *, cp);
} while (cp && cp != cplist);
}
retval = 1;
done:
return retval;
fail:
retval = 0;
goto done;
}
/*! Search XML tree using (internal) Clixon path struct
*
* @param[in] xt Top xml-tree where to search
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[in] cplist Lisp of clixon-path
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use
* @retval -1 Error
* @retval 0 Fail fail: eg no yang
* @retval 1 OK with found xml nodes in xvec (if any)
*/
static int
clixon_path_search(cxobj *xt,
yang_stmt *yt,
clixon_path *cplist,
clixon_xvec **xvec0)
{
int retval = -1;
char *modns; /* Module namespace of api-path */
clixon_path *cp;
clixon_xvec *xvecp = NULL;
clixon_xvec *xvecc = NULL;
yang_stmt *yc;
cxobj *xp;
int i;
cg_var *cv;
if ((xvecp = clixon_xvec_new()) == NULL)
goto done;
modns = NULL;
if ((cp = cplist) != NULL){
if (clixon_xvec_append(xvecp, xt) < 0)
goto done;
do {
yc = cp->cp_yang;
if ((modns = yang_find_mynamespace(yc)) == NULL)
goto fail;
if (xvecp){
if ((xvecc = clixon_xvec_new()) == NULL)
goto done;
for (i=0; i<clixon_xvec_len(xvecp); i++){
xp = clixon_xvec_i(xvecp, i); /* Iterate over parent set */
if (cp->cp_cvk && /* cornercase for instance-id [<pos>] special case */
(yang_keyword_get(yc) == Y_LIST || yang_keyword_get(yc) == Y_LEAF_LIST) &&
cvec_len(cp->cp_cvk) == 1 && (cv = cvec_i(cp->cp_cvk,0)) &&
(cv_type_get(cv) == CGV_UINT32)){
if (clixon_xml_find_pos(xp, yc, cv_uint32_get(cv), xvecc) < 0)
goto done;
}
else if (clixon_xml_find_index(xp, yang_parent_get(yc),
modns, yang_argument_get(yc),
cp->cp_cvk, xvecc) < 0)
goto done;
/* XXX: xvecc append! either here or in the functions */
} /* for */
}
if (xvecp)
clixon_xvec_free(xvecp);
xvecp = xvecc;
xvecc = NULL;
cp = NEXTQ(clixon_path *, cp);
} while (cp && cp != cplist);
*xvec0 = xvecp;
xvecp = NULL;
} /* if */
retval = 1;
done:
if (xvecp)
clixon_xvec_free(xvecp);
if (xvecc)
clixon_xvec_free(xvecc);
return retval;
fail: /* eg no yang for api-path, no match */
*xvec0 = NULL;
retval = 0;
goto done;
}
/*! Given RESTCONF api-path and an XML tree, return matching xml node vector using stdarg
*
* @param[in] xt Top xml-tree where to search
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use
* @param[in] format Format string for api-path syntax
* @retval -1 Error
* @retval 0 Non-fatal failure, yang bind failures, etc,
* @retval 1 OK with found xml nodes in xvec (if any) (xvec contains matches)
* Reasons for nomatch (retval = 0) are:
* - Modulename in api-path does not correspond to existing module
* - Modulename not defined for top-level id.
* - Number of keys in key-value list does not match Yang list
* @code
* cxobj **vec = NULL;
* size_t len = 0;
* if (clixon_xml_find_api_path(x, yspec, &vec, &len, "/symbol/%s", "foo") < 0)
* err;
* for (i=0; i<len; i++){
* x = vec[i];
* ...
* }
* free(xvec);
* @endcode
* @see clixon_xml_find_instance_id
*/
int
clixon_xml_find_api_path(cxobj *xt,
yang_stmt *yt,
cxobj ***xvec,
int *xlen,
const char *format,
...)
{
int retval = -1;
va_list ap;
size_t len;
char *api_path = NULL;
clixon_path *cplist = NULL;
int ret;
clixon_xvec *xv = NULL;
va_start(ap, format);
len = vsnprintf(NULL, 0, format, ap);
va_end(ap);
/* allocate an api-path string exactly fitting the length */
if ((api_path = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: actually compute api-path string content */
va_start(ap, format);
if (vsnprintf(api_path, len+1, format, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
va_end(ap);
goto done;
}
va_end(ap);
/* Parse api-path string to structured clixon-path data */
if (api_path_parse(api_path, &cplist) < 0)
goto done;
if (clicon_debug_get())
clixon_path_print(stderr, cplist);
/* Resolve module:name to yang-stmt, fail if not successful */
if ((ret = api_path_resolve(cplist, yt)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((ret = clixon_path_search(xt, yt, cplist, &xv)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Convert to api xvec format */
if (clixon_xvec_extract(xv, xvec, xlen) < 0)
goto done;
retval = 1;
done:
if (xv)
clixon_xvec_free(xv);
if (cplist)
clixon_path_free(cplist);
if (api_path)
free(api_path);
return retval;
fail:
retval = 0;
goto done;
}
/*! Given (instance-id) path and XML tree, return matching xml node vector using stdarg
*
* Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for
* example.
* @param[in] xt Top xml-tree where to search
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use
* @param[out] xlen Returns length of vector in return value
* @param[in] format Format string for api-path syntax
* @retval -1 Error
* @retval 0 Non-fatal failure, yang bind failures, etc,
* @retval 1 OK with found xml nodes in xvec (if any)
* Reasons for nomatch (retval = 0) are:
* - Modulename in api-path does not correspond to existing module
* - Modulename not defined for top-level id.
* - Number of keys in key-value list does not match Yang list
* @code
* cxobj **vec = NULL;
* int len = 0;
* if (clixon_xml_find_instance_id(x, yspec, &vec, &len, "/symbol/%s", "foo") < 0)
* goto err;
* for (i=0; i<len; i++){
* x = vec[i];
* ...
* }
* clixon_xvec_free(xvec);
* @endcode
* @note canonical namespace contexts are used, see xpath2canonical
* @see clixon_xml_find_api_path for RESTCONF api-paths
* @see RFC7950 Sec 9.13
*/
int
clixon_xml_find_instance_id(cxobj *xt,
yang_stmt *yt,
cxobj ***xvec,
int *xlen,
const char *format,
...)
{
int retval = -1;
va_list ap;
size_t len;
char *path = NULL;
clixon_path *cplist = NULL;
int ret;
clixon_xvec *xv = NULL;
va_start(ap, format);
len = vsnprintf(NULL, 0, format, ap);
va_end(ap);
/* allocate a path string exactly fitting the length */
if ((path = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: actually compute api-path string content */
va_start(ap, format);
if (vsnprintf(path, len+1, format, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
va_end(ap);
goto done;
}
va_end(ap);
if (instance_id_parse(path, &cplist) < 0)
goto done;
if (clicon_debug_get())
clixon_path_print(stderr, cplist);
/* Resolve module:name to pointer to yang-stmt, fail if not successful */
if ((ret = instance_id_resolve(cplist, yt)) < 0)
goto done;
if (ret == 0)
goto fail;
if ((ret = clixon_path_search(xt, yt, cplist, &xv)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Convert to api xvec format */
if (xv && clixon_xvec_extract(xv, xvec, xlen) < 0)
goto done;
retval = 1;
done:
if (xv)
clixon_xvec_free(xv);
if (cplist)
clixon_path_free(cplist);
if (path)
free(path);
return retval;
fail:
retval = 0;
goto done;
}
/*! Given (instance-id) path and YANG, parse path, resolve YANG and return namespace binding
*
* Instance-identifier is a subset of XML XPaths and defined in Yang, used in NACM for
* example.
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
* @param[out] nsctx Namespace context (should be created on entry)
* @param[in] format Format string for api-path syntax
* @retval -1 Error
* @retval 0 Non-fatal failure, yang bind failures, etc,
* @retval 1 OK with found xml nodes in xvec (if any)
* Reasons for nomatch (retval = 0) are:
* - Modulename in api-path does not correspond to existing module
* - Modulename not defined for top-level id.
* - Number of keys in key-value list does not match Yang list
* @code
* cvec *nsctx = NULL;
*
* nsctx = cvec_new(0);
* if (clixon_instance_id_bind(yspec, nsctx, "/symbol/%s", "foo") < 0)
* goto err;
* ...
* cvec_free(nsctx);
* @endcode
* @note canonical namespace contexts are used, see xpath2canonical
* @see clixon_xml_find_instance_id for finding XML nodes using instance-id:s
* @see RFC7950 Sec 9.13
*/
int
clixon_instance_id_bind(yang_stmt *yt,
cvec *nsctx,
const char *format,
...)
{
int retval = -1;
va_list ap;
size_t len;
char *path = NULL;
clixon_path *cplist = NULL;
clixon_path *cp;
int ret;
char *namespace;
va_start(ap, format);
len = vsnprintf(NULL, 0, format, ap);
va_end(ap);
/* allocate a path string exactly fitting the length */
if ((path = malloc(len+1)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
/* second round: actually compute api-path string content */
va_start(ap, format);
if (vsnprintf(path, len+1, format, ap) < 0){
clicon_err(OE_UNIX, errno, "vsnprintf");
va_end(ap);
goto done;
}
va_end(ap);
if (instance_id_parse(path, &cplist) < 0)
goto done;
if (clicon_debug_get())
clixon_path_print(stderr, cplist);
/* Resolve module:name to pointer to yang-stmt, fail if not successful */
if ((ret = instance_id_resolve(cplist, yt)) < 0)
goto done;
if (ret == 0)
goto fail;
/* Loop through prefixes used */
if ((cp = cplist) != NULL){
do {
if (cp->cp_prefix &&
cp->cp_yang &&
(namespace = yang_find_mynamespace(cp->cp_yang)) != NULL){
if (xml_nsctx_get(nsctx, cp->cp_prefix) == NULL)
if (xml_nsctx_add(nsctx, cp->cp_prefix, namespace) < 0)
goto done;
}
cp = NEXTQ(clixon_path *, cp);
} while (cp && cp != cplist);
}
retval = 1;
done:
if (cplist)
clixon_path_free(cplist);
if (path)
free(path);
return retval;
fail:
retval = 0;
goto done;
}