Renamed functions clicon->clixon, replaced global variables w access functions Unified clicon_netconf_error with clixon_err()
2004 lines
66 KiB
C
2004 lines
66 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2009-2019 Olof Hagsand
|
|
Copyright (C) 2020-2022 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 <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_debug.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_xpath_ctx.h"
|
|
#include "clixon_xpath.h"
|
|
#include "clixon_netconf_lib.h"
|
|
#include "clixon_xml_map.h"
|
|
#include "clixon_yang_module.h"
|
|
#include "clixon_yang_schema_mount.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,};
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%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 */
|
|
clixon_log(NULL, LOG_NOTICE, "API-PATH error: on line %d", ay.ay_linenum);
|
|
if (clixon_err_category() == 0)
|
|
clixon_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,};
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%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 */
|
|
clixon_log(NULL, LOG_NOTICE, "Instance-id error: on line %d", iy.iy_linenum);
|
|
if (clixon_err_category() == 0)
|
|
clixon_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;
|
|
}
|
|
|
|
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
|
|
* @retval 0 OK
|
|
*/
|
|
int
|
|
xml_yang_root(cxobj *x,
|
|
cxobj **xr)
|
|
{
|
|
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;
|
|
return 0;
|
|
}
|
|
|
|
/*! 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;
|
|
enum rfc_6020 ypkeyw = -1;
|
|
|
|
if ((yp = yang_parent_get(ys)) == NULL){
|
|
clixon_err(OE_YANG, EINVAL, "yang expected parent %s", yang_argument_get(ys));
|
|
goto done;
|
|
}
|
|
if (yp)
|
|
ypkeyw = yang_keyword_get(yp);
|
|
switch (ypkeyw){
|
|
case Y_MODULE:
|
|
case Y_SUBMODULE:
|
|
cprintf(cb, "/%s:", yang_argument_get(yp));
|
|
break;
|
|
case Y_GROUPING: /* mainly for expand-grouping in clixon-autocli.yang */
|
|
cprintf(cb, "/");
|
|
break;
|
|
default:
|
|
if (yang2api_path_fmt_1((yang_stmt *)yp, 1, cb) < 0) /* recursive call */
|
|
goto done;
|
|
if (ypkeyw != Y_CHOICE && ypkeyw != 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 &&
|
|
ypkeyw == Y_LIST &&
|
|
yang_key_match(yp, ys->ys_argument, NULL) == 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));
|
|
break;
|
|
}
|
|
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 &&
|
|
ypkeyw == Y_LIST){
|
|
if (yang_key_match(yp, yang_argument_get(ys), NULL) == 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.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* "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){
|
|
clixon_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){
|
|
clixon_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] cvv_i 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;
|
|
size_t len;
|
|
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
j = 1; /* j==0 is cli string */
|
|
len = strlen(api_path_fmt);
|
|
for (i=0; i<len; i++){
|
|
c = api_path_fmt[i];
|
|
if (esc){
|
|
esc = 0;
|
|
if (c!='s')
|
|
continue;
|
|
if (j == cvec_len(cvv)) /* last element */
|
|
;
|
|
else{
|
|
if ((cv = cvec_i(cvv, j++)) == NULL){
|
|
clixon_err(OE_XML, 0, "Number of elements in cvv does not match api_path_fmt string");
|
|
goto done;
|
|
}
|
|
if ((str = cv2str_dup(cv)) == NULL){
|
|
clixon_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){
|
|
clixon_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
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* 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;
|
|
size_t len;
|
|
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
j = 1; /* j==0 is cli string */
|
|
len = strlen(api_path_fmt);
|
|
for (i=0; i<len; 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){
|
|
clixon_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){
|
|
clixon_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
|
|
*
|
|
* @code
|
|
* cbuf *xpath = cbuf_new();
|
|
* cvec *cvv = NULL;
|
|
* cvec *nsc = NULL;
|
|
* if (uri_str2cvec("www.foo.com/restconf/a/b=c", '/', '=', 0, &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 clixon_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 *y1;
|
|
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;
|
|
char *val1;
|
|
char *decval;
|
|
int ret;
|
|
int root;
|
|
|
|
cprintf(xpath, "/");
|
|
/* Initialize namespace context */
|
|
if ((nsc = xml_nsctx_init(NULL, NULL)) == NULL)
|
|
goto done;
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
root = 1; /* root or mountpoint */
|
|
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;
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%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 (root && ymod) /* root */
|
|
y = yang_find_datanode(ymod, name);
|
|
else
|
|
y = yang_find_datanode(y, name);
|
|
root = 0;
|
|
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);
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%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 (cv_type_get(cv) == CGV_STRING){
|
|
/* val is uri percent encoded, eg x%2Cy,z */
|
|
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 */
|
|
if (i != offset)
|
|
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);
|
|
val1 = valvec[vi++];
|
|
/* valvec is uri encoded, needs decoding */
|
|
if (uri_percent_decode(val1, &decval) < 0)
|
|
goto done;
|
|
cprintf(xpath, "%s='%s']", cv_string_get(cvi), decval);
|
|
if (decval){
|
|
free(decval);
|
|
decval = NULL;
|
|
}
|
|
}
|
|
break;
|
|
case Y_LEAF_LIST: /* XXX: LOOP? */
|
|
if (i != offset)
|
|
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 x/y is mountpoint, pass mount yspec to children */
|
|
if ((ret = yang_schema_mount_point(y)) < 0)
|
|
goto done;
|
|
if (ret == 1){
|
|
y1 = NULL;
|
|
if (xml_nsctx_yangspec(yspec, &nsc) < 0)
|
|
goto done;
|
|
/* cf xml_bind_yang0_opt/xml_yang_mount_get */
|
|
if (yang_mount_get(y, cbuf_get(xpath), &y1) < 0)
|
|
goto done;
|
|
if (y1 != NULL){
|
|
y = y1;
|
|
yspec = y1;
|
|
root = 1;
|
|
}
|
|
}
|
|
if (prefix){
|
|
free(prefix);
|
|
prefix = NULL;
|
|
}
|
|
if (name){
|
|
free(name);
|
|
name = NULL;
|
|
}
|
|
} /* for */
|
|
retval = 1; /* OK */
|
|
if (nscp){
|
|
*nscp = nsc;
|
|
nsc = NULL;
|
|
}
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%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 cvec_free)
|
|
* @param[out] xerr Netconf error message
|
|
* @retval 1 OK
|
|
* @retval 0 Invalid api_path or associated XML, netconf called
|
|
* @retval -1 Fatal error
|
|
* @code
|
|
* char *xpath = NULL;
|
|
* cvec *nsc = NULL;
|
|
* cxobj *xerr = NULL;
|
|
* if ((ret = api_path2xpath("/module:a/b", yspec, &xpath, &nsc, &xerr)) < 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){
|
|
clixon_err(OE_XML, EINVAL, "api_path is NULL");
|
|
goto done;
|
|
}
|
|
/* Special case: "//" is not handled proerly by uri_str2cvec
|
|
* and is an invalid api-path
|
|
*/
|
|
if (strlen(api_path)>1 && api_path[0] == '/' && api_path[1] == '/'){
|
|
if (xerr && netconf_invalid_value_xml(xerr, "application", "Invalid api-path beginning with //") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
/* Split api-path into cligen variable vector,
|
|
* dont decode since api_path2xpath_cvv takes uri encode as input */
|
|
if (uri_str2cvec(api_path, '/', '=', 0, &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){
|
|
clixon_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 XML tree so far
|
|
* @param[in] y0 Yang spec for x0
|
|
* @param[in] nodeclass Set to schema nodes, data nodes, etc
|
|
* @param[in] strict Break if api-path is not "complete" otherwise ignore and continue
|
|
* @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
|
|
*
|
|
* @note both retval -1 set clixon_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;
|
|
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 *y1;
|
|
yang_stmt *ymod;
|
|
yang_stmt *ykey;
|
|
char *namespace = NULL;
|
|
cbuf *cberr = NULL;
|
|
char *val = NULL;
|
|
int ret;
|
|
char *xpath = NULL;
|
|
cvec *nsc = 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){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
/* restval is RFC 3896 encoded */
|
|
if ((restval = index(nodeid, '=')) != NULL){
|
|
*restval = '\0';
|
|
restval++;
|
|
}
|
|
/* 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){
|
|
clixon_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){
|
|
if (uri_percent_decode(restval, &val) < 0)
|
|
goto done;
|
|
}
|
|
if (val && xml_value_set(xb, val) < 0)
|
|
goto done;
|
|
break;
|
|
case Y_LIST:
|
|
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
|
|
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
|
|
* Note also that valvec entries are encoded
|
|
*/
|
|
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){
|
|
/* Here assign and decode key values */
|
|
val = NULL;
|
|
if (uri_percent_decode(valvec[vi-1], &val) < 0)
|
|
goto done;
|
|
if (xml_value_set(xb, val) < 0)
|
|
goto done;
|
|
if (val){
|
|
free(val);
|
|
val = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default: /* eg Y_CONTAINER, Y_LEAF */
|
|
if (restval != NULL){
|
|
if (strict){
|
|
cprintf(cberr, "malformed api-path, =%s not expected", restval);
|
|
if (xerr &&
|
|
netconf_malformed_message_xml(xerr, cbuf_get(cberr)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
}
|
|
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 x/y is mountpoint, pass mount yspec to children */
|
|
if ((ret = yang_schema_mount_point(y)) < 0)
|
|
goto done;
|
|
if (ret == 1){
|
|
y1 = NULL;
|
|
if (xml_nsctx_yangspec(ys_spec(y), &nsc) < 0)
|
|
goto done;
|
|
if (xml2xpath(x, nsc, 0, 1, &xpath) < 0) // XXX should be canonical
|
|
goto done;
|
|
if (xpath == NULL){
|
|
clixon_err(OE_YANG, 0, "No xpath from xml");
|
|
goto done;
|
|
}
|
|
/* cf xml_bind_yang0_opt/xml_yang_mount_get */
|
|
if (yang_mount_get(y, xpath, &y1) < 0)
|
|
goto done;
|
|
if (y1 != NULL)
|
|
y = y1;
|
|
}
|
|
if ((retval = api_path2xml_vec(vec+1, nvec-1,
|
|
x, y,
|
|
nodeclass, strict,
|
|
xbotp, ybotp, xerr)) < 1)
|
|
goto done;
|
|
ok:
|
|
retval = 1; /* OK */
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%s retval:%d", __FUNCTION__, retval);
|
|
if (xpath)
|
|
free(xpath);
|
|
if (nsc)
|
|
cvec_free(nsc);
|
|
if (cberr)
|
|
cbuf_free(cberr);
|
|
if (prefix)
|
|
free(prefix);
|
|
if (name)
|
|
free(name);
|
|
if (valvec)
|
|
free(valvec);
|
|
if (val)
|
|
free(val);
|
|
return retval;
|
|
fail:
|
|
retval = 0; /* invalid api-path or XML */
|
|
goto done;
|
|
}
|
|
|
|
/*! Create xml tree from api-path
|
|
*
|
|
* Create an XML tree from "scratch" using api-path, ie no other input than the api-path itself,
|
|
* ie not from an existing datastore for example.
|
|
* @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[in] strict Break if api-path is not "complete" otherwise ignore and continue
|
|
* @param[out] xbotp Resulting xml tree (end of xpath) (optional)
|
|
* @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
|
|
* @note both retval -1 set clixon_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?)
|
|
* @note "Collections" should use strict = 0
|
|
*/
|
|
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;
|
|
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%s api_path:%s", __FUNCTION__, api_path);
|
|
if ((cberr = cbuf_new()) == NULL){
|
|
clixon_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;
|
|
}
|
|
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 OK
|
|
* @retval 0 Fail
|
|
* @retval -1 Error
|
|
* 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;
|
|
cvec *cvk;
|
|
|
|
if ((cp = cplist) != NULL){
|
|
do {
|
|
if (yang_keyword_get(yt) == Y_SPEC){
|
|
if (cp->cp_prefix == NULL){
|
|
clixon_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){
|
|
clixon_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){
|
|
clixon_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){
|
|
cvk = yang_cvec_get(yc);
|
|
if (cvec_len(cp->cp_cvk) > cvec_len(cvk)){
|
|
clixon_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){
|
|
clixon_err(OE_YANG, ENOENT, "Unexpected named key %s (shouldnt happen)",
|
|
cv_name_get(cva));
|
|
goto fail;
|
|
}
|
|
cvy = cvec_i(cvk, i++);
|
|
cv_name_set(cva, cv_string_get(cvy));
|
|
}
|
|
}
|
|
else{
|
|
clixon_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] path Instance-id path original
|
|
* @param[in] yt Yang statement of top symbol (can be yang-spec if top-level)
|
|
* @retval 1 OK
|
|
* @retval 0 Fail error in xerr
|
|
* @retval -1 Error
|
|
* @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;
|
|
int ret;
|
|
|
|
yspec = ys_spec(yt);
|
|
if ((cp = cplist) != NULL){
|
|
do {
|
|
if (cp->cp_prefix == NULL){
|
|
clixon_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){
|
|
clixon_err(OE_YANG, ENOENT, "Prefix \"%s\" does not correspond to any existing module", cp->cp_prefix);
|
|
goto fail;
|
|
}
|
|
}
|
|
if ((ret = yang_schema_mount_point(yt)) < 0)
|
|
goto done;
|
|
if (ret == 1){
|
|
if (yang_mount_get_yspec_any(yt, &yspec) == 1){
|
|
if ((yt = yang_find_module_by_prefix_yspec(yspec, cp->cp_prefix)) == NULL){
|
|
clixon_err(OE_YANG, ENOENT, "Prefix \"%s\" does not correspond to any existing module", cp->cp_prefix);
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
if ((yc = yang_find_datanode(yt, cp->cp_id)) == NULL){
|
|
clixon_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 */
|
|
clixon_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))){
|
|
clixon_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){
|
|
clixon_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){
|
|
clixon_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 List of clixon-path
|
|
* @param[out] xvec Vector of xml-trees. Vector must be free():d after use
|
|
* @retval 1 OK with found xml nodes in xvec (if any)
|
|
* @retval 0 Fail fail: eg no yang
|
|
* @retval -1 Error
|
|
*/
|
|
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 OK with found xml nodes in xvec (if any) (xvec contains matches)
|
|
* @retval 0 Non-fatal failure, yang bind failures, etc,
|
|
* @retval -1 Error
|
|
* 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){
|
|
clixon_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){
|
|
clixon_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 (clixon_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, NULL) < 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 OK with found xml nodes in xvec (if any)
|
|
* @retval 0 Non-fatal failure, yang bind failures, etc,
|
|
* @retval -1 Error
|
|
* 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){
|
|
clixon_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){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (instance_id_parse(path, &cplist) < 0)
|
|
goto done;
|
|
if (clixon_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, NULL) < 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 xpath syntax
|
|
* @retval 1 OK with found xml nodes in xvec (if any)
|
|
* @retval 0 Non-fatal failure, yang bind failures, etc,
|
|
* @retval -1 Error
|
|
* 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){
|
|
clixon_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){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (instance_id_parse(path, &cplist) < 0)
|
|
goto done;
|
|
if (clixon_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;
|
|
}
|
|
|
|
/*! Given (instance-id) path and YANG, parse path, resolve YANG and return parse-tree
|
|
*
|
|
* 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] cplistp Path parse-tree
|
|
* @param[out] xerr Contains error if retval=0
|
|
* @param[in] format Format string for xpath syntax
|
|
* @retval 1 OK with found xml nodes in xvec (if any)
|
|
* @retval 0 Non-fatal failure, yang bind failures, etc,
|
|
* @retval -1 Error
|
|
*/
|
|
int
|
|
clixon_instance_id_parse(yang_stmt *yt,
|
|
clixon_path **cplistp,
|
|
cxobj **xerr,
|
|
const char *format,
|
|
...)
|
|
{
|
|
int retval = -1;
|
|
va_list ap;
|
|
size_t len;
|
|
char *path = NULL;
|
|
clixon_path *cplist = NULL;
|
|
int ret;
|
|
|
|
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){
|
|
clixon_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){
|
|
clixon_err(OE_UNIX, errno, "vsnprintf");
|
|
va_end(ap);
|
|
goto done;
|
|
}
|
|
va_end(ap);
|
|
if (instance_id_parse(path, &cplist) < 0)
|
|
goto done;
|
|
if (clixon_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){
|
|
if (xerr && netconf_invalid_value_xml(xerr, "application", clixon_err_reason()) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
if (cplistp){
|
|
*cplistp = cplist;
|
|
cplist = NULL;
|
|
}
|
|
retval = 1;
|
|
done:
|
|
if (cplist)
|
|
clixon_path_free(cplist);
|
|
if (path)
|
|
free(path);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|