diff --git a/CHANGELOG.md b/CHANGELOG.md
index 504a6eaa..776b84b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Clixon CHANGELOG
+- Generic map_str2int generic mapping tables
+
+- Removed vector return values from xmldb_get()
## 3.3.1 June 7 2017
diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c
index 671ff219..303e19b3 100644
--- a/apps/backend/backend_client.c
+++ b/apps/backend/backend_client.c
@@ -219,10 +219,20 @@ from_client_get_config(clicon_handle h,
clicon_err(OE_XML, 0, "db not found");
goto done;
}
+ if (xmldb_validate_db(db) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", db);
+ goto ok;
+ }
+
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((selector = xml_find_value(xfilter, "select"))==NULL)
selector="/";
- if (xmldb_get(h, db, selector, &xret, NULL, NULL) < 0){
+ if (xmldb_get(h, db, selector, &xret) < 0){
cprintf(cbret, ""
"operation-failed"
"application"
@@ -276,6 +286,16 @@ from_client_edit_config(clicon_handle h,
clicon_err(OE_XML, 0, "db not found");
goto done;
}
+ if (xmldb_validate_db(target) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", target);
+ goto ok;
+ }
+
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
@@ -356,6 +376,16 @@ from_client_lock(clicon_handle h,
"");
goto ok;
}
+ if (xmldb_validate_db(db) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", db);
+ goto ok;
+ }
+
/*
* A lock MUST not be granted if either of the following conditions is true:
* 1) A lock is already held by any NETCONF session or another entity.
@@ -410,6 +440,15 @@ from_client_unlock(clicon_handle h,
"");
goto ok;
}
+ if (xmldb_validate_db(db) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", db);
+ goto ok;
+ }
piddb = xmldb_islocked(h, db);
/*
* An unlock operation will not succeed if any of the following
@@ -534,6 +573,16 @@ from_client_copy_config(clicon_handle h,
"");
goto ok;
}
+ if (xmldb_validate_db(source) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", source);
+ goto ok;
+ }
+
if ((target = netconf_db_find(xe, "target")) == NULL){
cprintf(cbret, ""
"missing-element"
@@ -543,6 +592,15 @@ from_client_copy_config(clicon_handle h,
"");
goto ok;
}
+ if (xmldb_validate_db(target) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", target);
+ goto ok;
+ }
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
@@ -556,7 +614,6 @@ from_client_copy_config(clicon_handle h,
piddb);
goto ok;
}
-
if (xmldb_copy(h, source, target) < 0){
cprintf(cbret, ""
"operation-failed"
@@ -601,6 +658,16 @@ from_client_delete_config(clicon_handle h,
"");
goto ok;
}
+ if (xmldb_validate_db(target) < 0){
+ cprintf(cbret, ""
+ "invalid-value"
+ "protocol"
+ "error"
+ "No such database: %s"
+ "", target);
+ goto ok;
+ }
+
/* Check if target locked by other client */
piddb = xmldb_islocked(h, target);
if (piddb && mypid != piddb){
diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c
index 95b15869..ea352d85 100644
--- a/apps/backend/backend_commit.c
+++ b/apps/backend/backend_commit.c
@@ -146,9 +146,9 @@ validate_common(clicon_handle h,
goto done;
}
/* 2. Parse xml trees */
- if (xmldb_get(h, "running", "/", &td->td_src, NULL, NULL) < 0)
+ if (xmldb_get(h, "running", "/", &td->td_src) < 0)
goto done;
- if (xmldb_get(h, candidate, "/", &td->td_target, NULL, NULL) < 0)
+ if (xmldb_get(h, candidate, "/", &td->td_target) < 0)
goto done;
/* 3. Compute differences */
@@ -212,7 +212,8 @@ validate_common(clicon_handle h,
* The code reverts changes if the commit fails. But if the revert
* fails, we just ignore the errors and proceed. Maybe we should
* do something more drastic?
- * @param[in] h Clicon handle
+ * @param[in] h Clicon handle
+ * @param[in] candidate A candidate database, not necessarily "candidate"
*/
int
candidate_commit(clicon_handle h,
@@ -283,17 +284,17 @@ from_client_commit(clicon_handle h,
piddb);
goto ok;
}
-
if (candidate_commit(h, "candidate") < 0){
clicon_debug(1, "Commit candidate failed");
- /* XXX: candidate_validate should have proper error handling */
cprintf(cbret, ""
- "missing-attribute"
+ "invalid-value"
"protocol"
"error"
"%s"
"",
clicon_err_reason);
+ goto ok;
+
goto ok;
}
cprintf(cbret, "");
diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c
index 52f11bdc..ab9843d6 100644
--- a/apps/backend/backend_main.c
+++ b/apps/backend/backend_main.c
@@ -212,7 +212,7 @@ done:
static int
candb_reset(clicon_handle h)
{
- int retval = -1;
+ int retval = -1;
if (xmldb_copy(h, "running", "tmp") < 0){
clicon_err(OE_UNIX, errno, "file copy");
@@ -590,7 +590,7 @@ main(int argc, char **argv)
*(argv-1) = tmp;
if (reload_running){
- /* This could be afailed validation, and we should not fail for that */
+ /* This could be a failed validation, and we should not fail for that */
(void)candidate_commit(h, "candidate");
}
diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c
index 71f58047..86fe558d 100644
--- a/apps/cli/cli_common.c
+++ b/apps/cli/cli_common.c
@@ -655,9 +655,9 @@ compare_dbs(clicon_handle h,
astext = cv_int32_get(cvec_i(argv, 0));
else
astext = 0;
- if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0)
+ if (clicon_rpc_get_config(h, "running", "/", 0, &xc1) < 0)
goto done;
- if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0)
+ if (clicon_rpc_get_config(h, "candidate", "/", 0, &xc2) < 0)
goto done;
if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */
goto done;
@@ -823,7 +823,7 @@ save_config_file(clicon_handle h,
goto done;
}
filename = cv_string_get(cv);
- if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0)
+ if (clicon_rpc_get_config(h, dbstr,"/", 0, &xt) < 0)
goto done;
if ((f = fopen(filename, "wb")) == NULL){
clicon_err(OE_CFG, errno, "Creating file %s", filename);
@@ -1162,7 +1162,7 @@ cli_copy_config(clicon_handle h,
cprintf(cb, xpath, keyname, fromname);
/* Get from object configuration and store in x1 */
- if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0)
+ if (clicon_rpc_get_config(h, db, cbuf_get(cb), 0, &x1) < 0)
goto done;
/* Get to variable -> cv -> to name */
diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c
index dcdb1730..394c5d03 100644
--- a/apps/cli/cli_plugin.c
+++ b/apps/cli/cli_plugin.c
@@ -657,7 +657,7 @@ clicon_parse(clicon_handle h,
}
res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv);
if (res != CG_MATCH)
- pt_expand_cleanup_1(pt);
+ pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */
if (msav){
cli_tree_active_set(h, msav);
free(msav);
@@ -689,7 +689,7 @@ clicon_parse(clicon_handle h,
}
if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0)
cli_handler_err(stdout);
- pt_expand_cleanup_1(pt);
+ pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */
if (result)
*result = r;
goto done;
diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c
index f86fc9da..62a6ac3f 100644
--- a/apps/cli/cli_show.c
+++ b/apps/cli/cli_show.c
@@ -140,7 +140,7 @@ expand_dbvar(void *h,
if (api_path_fmt2xpath(api_path, cvv, &xpath) < 0)
goto done;
/* XXX read whole configuration, why not send xpath? */
- if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0)
+ if (clicon_rpc_get_config(h, dbstr, "/", 0, &xt) < 0)
goto done;
/* One round to detect duplicates
* XXX The code below would benefit from some cleanup
@@ -426,7 +426,7 @@ cli_show_config(clicon_handle h,
else
cprintf(cbxpath, "%s", xpath);
/* Get configuration from database */
- if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0)
+ if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), 0, &xt) < 0)
goto done;
/* Print configuration according to format */
switch (format){
@@ -505,7 +505,7 @@ show_conf_xpath(clicon_handle h,
}
cv = cvec_find_var(cvv, "xpath");
xpath = cv_string_get(cv);
- if (clicon_rpc_get_config(h, str, xpath, &xt) < 0)
+ if (clicon_rpc_get_config(h, str, xpath, 0, &xt) < 0)
goto done;
if (xpath_vec(xt, xpath, &xv, &xlen) < 0)
goto done;
diff --git a/apps/restconf/README.md b/apps/restconf/README.md
index 9dfb15d4..ab941091 100644
--- a/apps/restconf/README.md
+++ b/apps/restconf/README.md
@@ -60,7 +60,7 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et
}
]
-curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}}' http://localhost/restconf/data
+curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data
```
### Debugging
diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c
index f24cce22..86dbe801 100644
--- a/apps/restconf/restconf_methods.c
+++ b/apps/restconf/restconf_methods.c
@@ -156,6 +156,10 @@ api_data_get_gen(clicon_handle h,
cxobj **vec = NULL;
yang_spec *yspec;
cxobj *xret = NULL;
+ cxobj *xerr;
+ cbuf *cbj = NULL;;
+ int code;
+ char *reason_phrase;
clicon_debug(1, "%s", __FUNCTION__);
yspec = clicon_dbspec_yang(h);
@@ -166,16 +170,45 @@ api_data_get_gen(clicon_handle h,
goto done;
}
clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path));
- if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){
+ if (clicon_rpc_get_config(h, "running", cbuf_get(path), 1, &xret) < 0){
notfound(r);
goto done;
}
+#if 0 /* DEBUG */
{
cbuf *cb = cbuf_new();
- clicon_xml2cbuf(cb, xret, 0, 0);
+ xml2json_cbuf(cb, xret, 1);
clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb));
cbuf_free(cb);
}
+#endif
+ if (strcmp(xml_name(xret), "rpc-error") == 0){
+ if ((cbj = cbuf_new()) == NULL)
+ goto done;
+ if ((xerr = xpath_first(xret, "/error-tag")) == NULL){
+ notfound(r); /* bad reply? */
+ goto done;
+ }
+ code = clicon_str2int(netconf_restconf_map, xml_body(xerr));
+ if ((reason_phrase = clicon_int2str(http_reason_phrase_map, code)) == NULL)
+ reason_phrase="";
+ clicon_debug(1, "%s code:%d reason phrase:%s",
+ __FUNCTION__, code, reason_phrase);
+
+ if (xml_name_set(xret, "error") < 0)
+ goto done;
+ if (xml2json_cbuf(cbj, xret, 1) < 0)
+ goto done;
+ FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase);
+ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+json\r\n\r\n");
+ FCGX_FPrintF(r->out, "\r\n");
+ FCGX_FPrintF(r->out, "{\r\n");
+ FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {\r\n");
+ FCGX_FPrintF(r->out, " %s", cbuf_get(cbj));
+ FCGX_FPrintF(r->out, " }\r\n");
+ FCGX_FPrintF(r->out, "}\r\n");
+ goto ok;
+ }
if ((cbx = cbuf_new()) == NULL)
goto done;
FCGX_SetExitStatus(200, r->out); /* OK */
@@ -197,6 +230,8 @@ api_data_get_gen(clicon_handle h,
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (cbx)
cbuf_free(cbx);
+ if (cbj)
+ cbuf_free(cbj);
if (path)
cbuf_free(path);
if (xret)
@@ -555,6 +590,7 @@ api_data_delete(clicon_handle h,
goto done;
if ((cbx = cbuf_new()) == NULL)
goto done;
+
if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0)
goto done;
if (clicon_rpc_edit_config(h, "candidate",
diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c
index d2a023f8..368f59bd 100644
--- a/datastore/datastore_client.c
+++ b/datastore/datastore_client.c
@@ -213,7 +213,7 @@ main(int argc, char **argv)
if (strcmp(cmd, "get")==0){
if (argc != 1 && argc != 2)
usage(argv0);
- if (xmldb_get(h, db, argc==2?argv[1]:"/", &xt, NULL, 0) < 0)
+ if (xmldb_get(h, db, argc==2?argv[1]:"/", &xt) < 0)
goto done;
clicon_xml2file(stdout, xt, 0, 0);
diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c
index d546aac1..5739c78f 100644
--- a/datastore/keyvalue/clixon_keyvalue.c
+++ b/datastore/keyvalue/clixon_keyvalue.c
@@ -571,38 +571,14 @@ kv_setopt(xmldb_handle xh,
/*! Get content of database using xpath. return a set of matching sub-trees
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
- * @param[in] dbname Name of database to search in (filename including dir path
- * @param[in] xpath String with XPATH syntax. or NULL for all
- * @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
- * @param[out] xvec Vector of xml trees. Free after use.
- * @param[out] xlen Length of vector.
- * @retval 0 OK
- * @retval -1 Error
- * @code
- * cxobj *xt;
- * cxobj **xvec;
- * size_t xlen;
- * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
- * &xt, &xvec, &xlen) < 0)
- * err;
- * for (i=0; i17", &xt) < 0)
- * err;
- * if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0)
- * err;
- * @endcode
- * @see xmldb_put_xkey for single key
+ * This is a clixon datastore plugin of the the xmldb api
+ * @see xmldb_put
*/
int
kv_put(xmldb_handle xh,
char *db,
enum operation_type op,
- cxobj *xt)
+ cxobj *xt)
{
int retval = -1;
struct kv_handle *kh = handle(xh);
diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h
index a2a77e6c..d46de46f 100644
--- a/datastore/keyvalue/clixon_keyvalue.h
+++ b/datastore/keyvalue/clixon_keyvalue.h
@@ -39,8 +39,7 @@
/*
* Prototypes
*/
-int kv_get(xmldb_handle h, char *db, char *xpath,
- cxobj **xtop, cxobj ***xvec, size_t *xlen);
+int kv_get(xmldb_handle h, char *db, char *xpath, cxobj **xtop);
int kv_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int kv_dump(FILE *f, char *dbfilename, char *rxkey);
int kv_copy(xmldb_handle h, char *from, char *to);
diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c
index 742ea639..e35b5c85 100644
--- a/datastore/text/clixon_xmldb_text.c
+++ b/datastore/text/clixon_xmldb_text.c
@@ -94,6 +94,7 @@ static int _running_locked = 0;
static int _candidate_locked = 0;
static int _startup_locked = 0;
+
/*! Translate from symbolic database name to actual filename in file-system
* @param[in] th text handle handle
* @param[in] db Symbolic database name, eg "candidate", "running"
@@ -107,8 +108,8 @@ static int _startup_locked = 0;
*/
static int
text_db2file(struct text_handle *th,
- char *db,
- char **filename)
+ char *db,
+ char **filename)
{
int retval = -1;
cbuf *cb;
@@ -122,13 +123,6 @@ text_db2file(struct text_handle *th,
clicon_err(OE_XML, errno, "dbdir not set");
goto done;
}
- if (strcmp(db, "running") != 0 &&
- strcmp(db, "candidate") != 0 &&
- strcmp(db, "startup") != 0 &&
- strcmp(db, "tmp") != 0){
- clicon_err(OE_XML, 0, "No such database: %s", db);
- goto done;
- }
cprintf(cb, "%s/%s_db", dir, db);
if ((*filename = strdup4(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
@@ -318,39 +312,14 @@ singleconfigroot(cxobj *xt,
/*! Get content of database using xpath. return a set of matching sub-trees
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
- * @param[in] xh XMLDB handle
- * @param[in] dbname Name of database to search in (filename including dir path
- * @param[in] xpath String with XPATH syntax. or NULL for all
- * @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
- * @param[out] xvec Vector of xml trees. Free after use.
- * @param[out] xlen Length of vector.
- * @retval 0 OK
- * @retval -1 Error
- * @code
- * cxobj *xt;
- * cxobj **xvec;
- * size_t xlen;
- * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
- * &xt, &xvec, &xlen) < 0)
- * err;
- * for (i=0; i1)
clicon_xml2file(stderr, xt, 0, 1);
- if (xvec0 && xlen0){
- *xvec0 = xvec;
- xvec = NULL;
- *xlen0 = xlen;
- xlen = 0;
- }
*xtop = xt;
xt = NULL;
retval = 0;
@@ -735,30 +698,14 @@ text_modify_top(cxobj *x0,
/*! Modify database provided an xml tree and an operation
- *
- * @param[in] xh XMLDB handle
- * @param[in] db running or candidate
- * @param[in] op OP_MERGE: just add it.
- * OP_REPLACE: first delete whole database
- * OP_NONE: operation attribute in xml determines operation
- * @param[in] x1 xml-tree to merge/replace. Top-level symbol is 'config'.
- * Should be empty or '' if delete?
- * @retval 0 OK
- * @retval -1 Error
- * The xml may contain the "operation" attribute which defines the operation.
- * @code
- * cxobj *xt;
- * if (clicon_xml_parse_str("17", &xt) < 0)
- * err;
- * if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0)
- * err;
- * @endcode
-y */
+ * This is a clixon datastore plugin of the the xmldb api
+ * @see xmldb_put
+ */
int
text_put(xmldb_handle xh,
char *db,
enum operation_type op,
- cxobj *x1)
+ cxobj *x1)
{
int retval = -1;
struct text_handle *th = handle(xh);
@@ -799,7 +746,6 @@ text_put(xmldb_handle xh,
}
/* 2. File is not empty ... -> replace root */
else{
-
/* There should only be one element and called config */
if (singleconfigroot(x0, &x0) < 0)
goto done;
@@ -871,8 +817,8 @@ text_put(xmldb_handle xh,
*/
int
text_copy(xmldb_handle xh,
- char *from,
- char *to)
+ char *from,
+ char *to)
{
int retval = -1;
struct text_handle *th = handle(xh);
@@ -904,11 +850,10 @@ text_copy(xmldb_handle xh,
*/
int
text_lock(xmldb_handle xh,
- char *db,
- int pid)
+ char *db,
+ int pid)
{
// struct text_handle *th = handle(xh);
-
if (strcmp("running", db) == 0)
_running_locked = pid;
else if (strcmp("candidate", db) == 0)
@@ -929,10 +874,9 @@ text_lock(xmldb_handle xh,
*/
int
text_unlock(xmldb_handle xh,
- char *db)
+ char *db)
{
// struct text_handle *th = handle(xh);
-
if (strcmp("running", db) == 0)
_running_locked = 0;
else if (strcmp("candidate", db) == 0)
@@ -993,8 +937,8 @@ text_islocked(xmldb_handle xh,
* @retval 1 Yes it exists
*/
int
-text_exists(xmldb_handle xh,
- char *db)
+text_exists(xmldb_handle xh,
+ char *db)
{
int retval = -1;
@@ -1022,7 +966,7 @@ text_exists(xmldb_handle xh,
*/
int
text_delete(xmldb_handle xh,
- char *db)
+ char *db)
{
int retval = -1;
char *filename = NULL;
@@ -1049,7 +993,7 @@ text_delete(xmldb_handle xh,
*/
int
text_create(xmldb_handle xh,
- char *db)
+ char *db)
{
int retval = -1;
struct text_handle *th = handle(xh);
diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h
index 7a53fdec..b4487757 100644
--- a/datastore/text/clixon_xmldb_text.h
+++ b/datastore/text/clixon_xmldb_text.h
@@ -39,8 +39,7 @@
/*
* Prototypes
*/
-int text_get(xmldb_handle h, char *db, char *xpath,
- cxobj **xtop, cxobj ***xvec, size_t *xlen);
+int text_get(xmldb_handle h, char *db, char *xpath, cxobj **xtop);
int text_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt);
int text_dump(FILE *f, char *dbfilename, char *rxkey);
int text_copy(xmldb_handle h, char *from, char *to);
diff --git a/example/ietf-ip@2014-06-16.yang b/example/ietf-ip@2014-06-16.yang
index 8e39326d..ce235e12 100644
--- a/example/ietf-ip@2014-06-16.yang
+++ b/example/ietf-ip@2014-06-16.yang
@@ -1,4 +1,4 @@
- module ietf-ip {
+module ietf-ip {
namespace "urn:ietf:params:xml:ns:yang:ietf-ip";
prefix ip;
diff --git a/example/routing_cli.c b/example/routing_cli.c
index 3515408a..0cfbdfe7 100644
--- a/example/routing_cli.c
+++ b/example/routing_cli.c
@@ -81,8 +81,9 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv)
/* Show eth0 interfaces config using XPATH */
if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]",
- &xret) < 0)
+ 0, &xret) < 0)
goto done;
+
xml_print(stdout, xret);
retval = 0;
done:
diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h
index c19997d1..bac31496 100644
--- a/lib/clixon/clixon_proto_client.h
+++ b/lib/clixon/clixon_proto_client.h
@@ -45,7 +45,8 @@ int clicon_rpc_msg(clicon_handle h, struct clicon_msg *msg, cxobj **xret0,
int clicon_rpc_netconf(clicon_handle h, char *xmlst, cxobj **xret, int *sp);
int clicon_rpc_netconf_xml(clicon_handle h, cxobj *xml, cxobj **xret, int *sp);
int clicon_rpc_generate_error(cxobj *xerr);
-int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath, cxobj **xret);
+int clicon_rpc_get_config(clicon_handle h, char *db, char *xpath,
+ int errmode, cxobj **xret);
int clicon_rpc_edit_config(clicon_handle h, char *db, enum operation_type op,
char *xml);
int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2);
diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h
index e0c9f1b7..ba60e681 100644
--- a/lib/clixon/clixon_string.h
+++ b/lib/clixon/clixon_string.h
@@ -36,6 +36,23 @@
#ifndef _CLIXON_STRING_H_
#define _CLIXON_STRING_H_
+/* Struct used to map between int and strings. Typically used to map between
+ * values and their names. Note NULL terminated
+ * Example:
+ * @code
+static const map_str2int atmap[] = {
+ {"One", 1},
+ {"Two", 2},
+ {NULL, -1}
+};
+ * @endcode
+ */
+struct map_str2int{
+ char *ms_str;
+ int ms_int;
+};
+typedef struct map_str2int map_str2int;
+
/*! A malloc version that aligns on 4 bytes. To avoid warning from valgrind */
#define align4(s) (((s)/4)*4 + 4)
@@ -59,6 +76,9 @@ char *clicon_strjoin (int argc, char **argv, char *delim);
int str2cvec(char *string, char delim1, char delim2, cvec **cvp);
int percent_encode(char *str, char **escp);
int percent_decode(char *esc, char **str);
+const char *clicon_int2str(const map_str2int *mstab, int i);
+int clicon_str2int(const map_str2int *mstab, char *str);
+
#ifndef HAVE_STRNDUP
char *clicon_strndup (const char *, size_t);
#endif /* ! HAVE_STRNDUP */
diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h
index 8cd645cb..0a2ce81a 100644
--- a/lib/clixon/clixon_xml_db.h
+++ b/lib/clixon/clixon_xml_db.h
@@ -75,12 +75,10 @@ typedef int (xmldb_getopt_t)(xmldb_handle xh, char *optname, void **value);
typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value);
/* Type of xmldb get function */
-typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath,
- cxobj **xtop, cxobj ***xvec, size_t *xlen);
+typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath, cxobj **xtop);
/* Type of xmldb put function */
-typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op,
- cxobj *xt);
+typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op, cxobj *xt);
/* Type of xmldb copy function */
typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to);
@@ -135,12 +133,12 @@ struct xmldb_api{
int xmldb_plugin_load(clicon_handle h, char *filename);
int xmldb_plugin_unload(clicon_handle h);
+int xmldb_validate_db(char *db);
int xmldb_connect(clicon_handle h);
int xmldb_disconnect(clicon_handle h);
int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
-int xmldb_get(clicon_handle h, char *db, char *xpath,
- cxobj **xtop, cxobj ***xvec, size_t *xlen);
+int xmldb_get(clicon_handle h, char *db, char *xpath, cxobj **xtop);
int xmldb_put(clicon_handle h, char *db, enum operation_type op, cxobj *xt);
int xmldb_copy(clicon_handle h, char *from, char *to);
int xmldb_lock(clicon_handle h, char *db, int pid);
diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h
index abcf26c5..6a24a5a0 100644
--- a/lib/clixon/clixon_xml_map.h
+++ b/lib/clixon/clixon_xml_map.h
@@ -47,7 +47,8 @@ enum {
LVXML_VECVAL, /* key: a.b.0{x=1} -> 1 och */
LVXML_VECVAL2, /* key: a.b.0{x=1} -> 1 och */
};
-
+extern const map_str2int netconf_restconf_map[];
+extern const map_str2int http_reason_phrase_map[];
/*
* Prototypes
diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c
index e21d9600..dfd50e1d 100644
--- a/lib/src/clixon_proto_client.c
+++ b/lib/src/clixon_proto_client.c
@@ -230,12 +230,14 @@ clicon_rpc_generate_error(cxobj *xerr)
* @param[in] h CLICON handle
* @param[in] db Name of database
* @param[in] xpath XPath (or "")
+ * @param[in] errmode 0 if xml errors are returned as clicon_err
+ * 1 if xml errors are in xt and return 0.
* @param[out] xt XML tree. must be freed by caller with xml_free
* @retval 0 OK
* @retval -1 Error, fatal or xml
* @code
* cxobj *xt = NULL;
- * if (clicon_rpc_get_config(h, "running", "/", &xt) < 0)
+ * if (clicon_rpc_get_config(h, "running", "/", 0, &xt) < 0)
* err;
* if (xt)
* xml_free(xt);
@@ -245,6 +247,7 @@ int
clicon_rpc_get_config(clicon_handle h,
char *db,
char *xpath,
+ int errmode,
cxobj **xt)
{
int retval = -1;
@@ -264,13 +267,22 @@ clicon_rpc_get_config(clicon_handle h,
goto done;
if (clicon_rpc_msg(h, msg, &xret, NULL) < 0)
goto done;
- if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
- clicon_rpc_generate_error(xerr);
- goto done;
- }
- if ((xd = xpath_first(xret, "//data/config")) == NULL)
- if ((xd = xml_new("config", NULL)) == NULL)
+ if (errmode == 0){ /* Move this to caller */
+ if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){
+ clicon_rpc_generate_error(xerr);
goto done;
+ }
+ if ((xd = xpath_first(xret, "//data/config")) == NULL)
+ if ((xd = xml_new("config", NULL)) == NULL)
+ goto done;
+ }
+ else{ /* Send xml error back (this should be default behaviour) */
+ if ((xd = xpath_first(xret, "//rpc-error")) == NULL){
+ if ((xd = xpath_first(xret, "//data/config")) == NULL)
+ if ((xd = xml_new("config", NULL)) == NULL)
+ goto done;
+ }
+ }
if (xt){
if (xml_rm(xd) < 0)
goto done;
diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c
index 06b9fc49..6ee4ee67 100644
--- a/lib/src/clixon_string.c
+++ b/lib/src/clixon_string.c
@@ -333,6 +333,43 @@ str2cvec(char *string,
goto done;
}
+/*! Map from int to string using str2int map
+ * @param[in] ms String, integer map
+ * @param[in] i Input integer
+ * @retval str String value
+ * @retval NULL Error, not found
+ * @note linear search
+ */
+const char *
+clicon_int2str(const map_str2int *mstab,
+ int i)
+{
+ const struct map_str2int *ms;
+
+ for (ms = &mstab[0]; ms->ms_str; ms++)
+ if (ms->ms_int == i)
+ return ms->ms_str;
+ return NULL;
+}
+
+/*! Map from string to int using str2int map
+ * @param[in] ms String, integer map
+ * @param[in] str Input string
+ * @retval int Value
+ * @retval -1 Error, not found
+ * @note linear search
+ */
+int
+clicon_str2int(const map_str2int *mstab,
+ char *str)
+{
+ const struct map_str2int *ms;
+
+ for (ms = &mstab[0]; ms->ms_str; ms++)
+ if (strcmp(ms->ms_str, str) == 0)
+ return ms->ms_int;
+ return -1;
+}
/*! strndup() for systems without it, such as xBSD
*/
diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c
index 0b4d3835..fc9b11d9 100644
--- a/lib/src/clixon_xml.c
+++ b/lib/src/clixon_xml.c
@@ -50,6 +50,7 @@
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
+#include "clixon_string.h"
#include "clixon_queue.h"
#include "clixon_xml.h"
#include "clixon_xml_parse.h"
@@ -82,14 +83,8 @@ struct xml{
cg_var *x_cv; /* If body this contains the typed value */
};
-/* Type to string conversion */
-struct map_str2int{
- char *ms_str;
- enum cxobj_type ms_type;
-};
-
/* Mapping between xml type <--> string */
-static const struct map_str2int xsmap[] = {
+static const map_str2int xsmap[] = {
{"error", CX_ERROR},
{"element", CX_ELMNT},
{"attr", CX_ATTR},
@@ -104,12 +99,7 @@ static const struct map_str2int xsmap[] = {
char *
xml_type2str(enum cxobj_type type)
{
- const struct map_str2int *xs;
-
- for (xs = &xsmap[0]; xs->ms_str; xs++)
- if (xs->ms_type == type)
- return xs->ms_str;
- return NULL;
+ return (char*)clicon_int2str(xsmap, type);
}
/*
diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c
index 0ed0bafe..08becf39 100644
--- a/lib/src/clixon_xml_db.c
+++ b/lib/src/clixon_xml_db.c
@@ -169,6 +169,23 @@ xmldb_plugin_unload(clicon_handle h)
return retval;
}
+/*! Validate database name
+ * @param[in] db Name of database
+ * @param[out] xret Return value as cligen buffer containing xml netconf return
+ * @retval 0 OK
+ * @retval -1 Failed validate, xret set to error
+ */
+int
+xmldb_validate_db(char *db)
+{
+ if (strcmp(db, "running") != 0 &&
+ strcmp(db, "candidate") != 0 &&
+ strcmp(db, "startup") != 0 &&
+ strcmp(db, "tmp") != 0)
+ return -1;
+ return 0;
+}
+
/*! Connect to a datastore plugin, allocate handle to be used in API calls
* @param[in] h Clicon handle
* @retval 0 OK
@@ -306,23 +323,17 @@ xmldb_setopt(clicon_handle h,
* @param[in] dbname Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[out] xtop Single XML tree which xvec points to. Free with xml_free()
- * @param[out] xvec Vector of xml trees. Free after use.
- * @param[out] xlen Length of vector.
* @retval 0 OK
* @retval -1 Error
* @code
* cxobj *xt;
- * cxobj **xvec;
- * size_t xlen;
- * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]",
- * &xt, &xvec, &xlen) < 0)
+ * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", &xt) < 0)
* err;
* for (i=0; ixa_get_fn(xh, db, xpath, xtop, xvec, xlen);
+ retval = xa->xa_get_fn(xh, db, xpath, xtop);
#if DEBUG
if (retval == 0) {
cbuf *cb = cbuf_new();
@@ -366,14 +375,12 @@ xmldb_get(clicon_handle h,
return retval;
}
-/*! Modify database provided an xml tree and an operation
+/*! Modify database given an xml tree and an operation
*
* @param[in] h CLICON handle
* @param[in] db running or candidate
+ * @param[in] op Top-level operation, can be superceded by other op in tree
* @param[in] xt xml-tree. Top-level symbol is dummy
- * @param[in] op OP_MERGE: just add it.
- * OP_REPLACE: first delete whole database
- * OP_NONE: operation attribute in xml determines operation
* @retval 0 OK
* @retval -1 Error
* The xml may contain the "operation" attribute which defines the operation.
@@ -581,7 +588,7 @@ xmldb_islocked(clicon_handle h,
clicon_err(OE_DB, 0, "Not connected to datastore plugin");
goto done;
}
- retval =xa->xa_islocked_fn(xh, db);
+ retval = xa->xa_islocked_fn(xh, db);
done:
return retval;
}
diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c
index 982aa3b3..bca2d162 100644
--- a/lib/src/clixon_xml_map.c
+++ b/lib/src/clixon_xml_map.c
@@ -1617,3 +1617,84 @@ api_path2xml(char *api_path,
free(vec);
return retval;
}
+
+/* See RFC 8040 Section 7: Mapping from NETCONF to Status Code
+ * and RFC 6241 Appendix A. NETCONF Error list
+ */
+const map_str2int netconf_restconf_map[] = {
+ {"in-use", 409},
+ {"invalid-value", 400},
+ {"invalid-value", 404},
+ {"invalid-value", 406},
+ {"too-big", 413}, /* request */
+ {"too-big", 400}, /* response */
+ {"missing-attribute", 400},
+ {"bad-attribute", 400},
+ {"unknown-attribute", 400},
+ {"bad-element", 400},
+ {"unknown-element", 400},
+ {"unknown-namespace", 400},
+ {"access-denied", 401},
+ {"access-denied", 403},
+ {"lock-denied", 409},
+ {"resource-denied", 409},
+ {"rollback-failed", 500},
+ {"data-exists", 409},
+ {"data-missing", 409},
+ {"operation-not-supported",405},
+ {"operation-not-supported",501},
+ {"operation-failed", 412},
+ {"operation-failed", 500},
+ {"partial-operation", 500},
+ {"malformed-message", 400},
+ {NULL, -1}
+};
+
+/* See 7231 Section 6.1
+ */
+const map_str2int http_reason_phrase_map[] = {
+ {"Continue", 100},
+ {"Switching Protocols", 101},
+ {"OK", 200},
+ {"Created", 201},
+ {"Accepted", 202},
+ {"Non-Authoritative Information", 203},
+ {"No Content", 204},
+ {"Reset Content", 205},
+ {"Partial Content", 206},
+ {"Multiple Choices", 300},
+ {"Moved Permanently", 301},
+ {"Found", 302},
+ {"See Other", 303},
+ {"Not Modified", 304},
+ {"Use Proxy", 305},
+ {"Temporary Redirect", 307},
+ {"Bad Request", 400},
+ {"Unauthorized", 401},
+ {"Payment Required", 402},
+ {"Forbidden", 403},
+ {"Not Found", 404},
+ {"Method Not Allowed", 405},
+ {"Not Acceptable", 406},
+ {"Proxy Authentication Required", 407},
+ {"Request Timeout", 408},
+ {"Conflict", 409},
+ {"Gone", 410},
+ {"Length Required", 411},
+ {"Precondition Failed", 412},
+ {"Payload Too Large", 413},
+ {"URI Too Long", 414},
+ {"Unsupported Media Type", 415},
+ {"Range Not Satisfiable", 416},
+ {"Expectation Failed", 417},
+ {"Upgrade Required", 426},
+ {"Internal Server Error", 500},
+ {"Not Implemented", 501},
+ {"Bad Gateway", 502},
+ {"Service Unavailable", 503},
+ {"Gateway Timeout", 504},
+ {"HTTP Version Not Supported", 505},
+ {NULL, -1}
+};
+
+
diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c
index b16da920..8963f803 100644
--- a/lib/src/clixon_xsl.c
+++ b/lib/src/clixon_xsl.c
@@ -100,6 +100,7 @@ in
/* clicon */
#include "clixon_err.h"
#include "clixon_log.h"
+#include "clixon_string.h"
#include "clixon_xml.h"
#include "clixon_xsl.h"
@@ -130,13 +131,8 @@ enum axis_type{
A_DESCENDANT_OR_SELF, /* actually descendant-or-self */
};
-struct map_str2int{
- char *ms_str; /* string as in 4.2.4 in RFC 6020 */
- int ms_int;
-};
-
/* Mapping between axis type string <--> int */
-static const struct map_str2int atmap[] = {
+static const map_str2int atmap[] = {
{"self", A_SELF},
{"child", A_CHILD},
{"parent", A_PARENT},
@@ -160,19 +156,6 @@ struct xpath_element{
static int xpath_split(char *xpathstr, char **pathexpr);
-static char *axis_type2str(enum axis_type type) __attribute__ ((unused));
-
-static char *
-axis_type2str(enum axis_type type)
-{
- const struct map_str2int *at;
-
- for (at = &atmap[0]; at->ms_str; at++)
- if (at->ms_int == type)
- return at->ms_str;
- return NULL;
-}
-
static int
xpath_print(FILE *f, struct xpath_element *xplist)
{
@@ -180,7 +163,7 @@ xpath_print(FILE *f, struct xpath_element *xplist)
struct xpath_predicate *xp;
for (xe=xplist; xe; xe=xe->xe_next){
- fprintf(f, "\t:%s %s ", axis_type2str(xe->xe_type),
+ fprintf(f, "\t:%s %s ", clicon_int2str(atmap, xe->xe_type),
xe->xe_str?xe->xe_str:"");
for (xp=xe->xe_predicate; xp; xp=xp->xp_next)
fprintf(f, "[%s]", xp->xp_expr);
@@ -598,7 +581,7 @@ xpath_find(struct xpath_element *xe,
}
#if 0
fprintf(stderr, "%s: %s: \"%s\"\n", __FUNCTION__,
- axis_type2str(xe->xe_type), xe->xe_str?xe->xe_str:"");
+ clicon_int2str(atmap, xe->xe_type), xe->xe_str?xe->xe_str:"");
#endif
switch (xe->xe_type){
case A_SELF:
diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c
index 14cead1f..7ca45e54 100644
--- a/lib/src/clixon_yang.c
+++ b/lib/src/clixon_yang.c
@@ -75,21 +75,9 @@
for static scope type binding */
#define YANG_TYPE_CACHE 1
-/*
- * Private data types
- */
-/* Struct used to map between int and strings. Used for:
- * - mapping yang types/typedefs (strings) and cligen types (ints).
- * - mapping yang keywords (strings) and enum (clicon)
- */
-struct map_str2int{
- char *ms_str; /* string as in 4.2.4 in RFC 6020 */
- int ms_int;
-};
-
/* Mapping between yang keyword string <--> clicon constants */
-static const struct map_str2int ykmap[] = {
+static const map_str2int ykmap[] = {
{"anyxml", Y_ANYXML},
{"argument", Y_ARGUMENT},
{"augment", Y_AUGMENT},
@@ -545,18 +533,10 @@ ys_flag_reset(yang_stmt *ys,
return 0;
}
-/*! Translate from RFC 6020 keywords to printable string.
- linear search,...
- */
char *
yang_key2str(int keyword)
{
- const struct map_str2int *yk;
-
- for (yk = &ykmap[0]; yk->ms_str; yk++)
- if (yk->ms_int == keyword)
- return yk->ms_str;
- return NULL;
+ return (char*)clicon_int2str(ykmap, keyword);
}
/*! Find top module or sub-module. Note that ultimate top is yang spec
diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c
index 6533ff7d..b92a1bbf 100644
--- a/lib/src/clixon_yang_type.c
+++ b/lib/src/clixon_yang_type.c
@@ -71,19 +71,10 @@
/*
* Local types and variables
*/
-/* Struct used to map between int and strings. Used for:
- * - mapping yang types/typedefs (strings) and cligen types (ints).
- * - mapping yang keywords (strings) and enum (clicon)
- * (same struct in clicon_yang.c)
- */
-struct map_str2int{
- char *ms_str; /* string as in 4.2.4 in RFC 6020 */
- int ms_int;
-};
/* Mapping between yang types <--> cligen types
Note, first match used wne translating from cv to yang --> order is significant */
-static const struct map_str2int ytmap[] = {
+static const map_str2int ytmap[] = {
{"int32", CGV_INT32}, /* NOTE, first match on right is significant, dont move */
{"string", CGV_STRING}, /* NOTE, first match on right is significant, dont move */
{"string", CGV_REST}, /* For cv -> yang translation of rest */
@@ -105,9 +96,22 @@ static const struct map_str2int ytmap[] = {
{"uint32", CGV_UINT32},
{"uint64", CGV_UINT64},
{"union", CGV_REST}, /* Is replaced by actual type */
- {NULL, -1}
+ {NULL, -1}
};
+/* return 1 if built-in, 0 if not */
+static int
+yang_builtin(char *type)
+{
+ const struct map_str2int *yt;
+
+ /* built-in types */
+ for (yt = &ytmap[0]; yt->ms_str; yt++)
+ if (strcmp(yt->ms_str, type) == 0)
+ return 1;
+ return 0;
+}
+
int
yang_type_cache_set(yang_type_cache **ycache0,
yang_stmt *resolved,
@@ -229,20 +233,6 @@ ys_resolve_type(yang_stmt *ys, void *arg)
return retval;
}
-
-/* return 1 if built-in, 0 if not */
-static int
-yang_builtin(char *type)
-{
- const struct map_str2int *yt;
-
- /* built-in types */
- for (yt = &ytmap[0]; yt->ms_str; yt++)
- if (strcmp(yt->ms_str, type) == 0)
- return 1;
- return 0;
-}
-
/*! Translate from a yang type to a cligen variable type
*
* Currently many built-in types from RFC6020 and some RFC6991 types.
@@ -252,17 +242,17 @@ yang_builtin(char *type)
* Return 0 if no match but set cv_type to CGV_ERR
*/
int
-yang2cv_type(char *ytype, enum cv_type *cv_type)
+yang2cv_type(char *ytype,
+ enum cv_type *cv_type)
{
- const struct map_str2int *yt;
+ int ret;
*cv_type = CGV_ERR;
/* built-in types */
- for (yt = &ytmap[0]; yt->ms_str; yt++)
- if (strcmp(yt->ms_str, ytype) == 0){
- *cv_type = yt->ms_int;
- return 0;
- }
+ if ((ret = clicon_str2int(ytmap, ytype)) != -1){
+ *cv_type = ret;
+ return 0;
+ }
/* special derived types */
if (strcmp("ipv4-address", ytype) == 0){ /* RFC6991 */
*cv_type = CGV_IPV4ADDR;
@@ -300,14 +290,13 @@ yang2cv_type(char *ytype, enum cv_type *cv_type)
char *
cv2yang_type(enum cv_type cv_type)
{
- const struct map_str2int *yt;
char *ytype;
+ const char *str;
ytype = "empty";
/* built-in types */
- for (yt = &ytmap[0]; yt->ms_str; yt++)
- if (yt->ms_int == cv_type)
- return yt->ms_str;
+ if ((str = clicon_int2str(ytmap, cv_type)) != NULL)
+ return (char*)str;
/* special derived types */
if (cv_type == CGV_IPV4ADDR) /* RFC6991 */
diff --git a/test/lib.sh b/test/lib.sh
index 423e0c22..074fb965 100755
--- a/test/lib.sh
+++ b/test/lib.sh
@@ -42,7 +42,7 @@ expectfn(){
# echo "expect:\"$expect\""
# echo "match:\"$match\""
if [ -z "$match" ]; then
- err $expect "$ret"
+ err "$expect" "$ret"
fi
if [ -n "$expect2" ]; then
match=`echo "$ret" | grep -EZo "$expect2"`