diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 9ecdf378..01eb48b1 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -89,7 +89,7 @@ usage(char *argv0) "\t-m \tYang module. Mandatory\n" "and command is either:\n" "\tget \n" - "\tput (set|merge|delete) \n" + "\tput (merge|replace|create|delete|remove) \n" "\tcopy \n" "\tlock \n" "\tunlock\n" @@ -120,8 +120,7 @@ main(int argc, char **argv) int ret; int pid; enum operation_type op; - cxobj *xt; - cxobj *xn; + cxobj *xt = NULL; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, CLICON_LOG_STDERR); @@ -212,26 +211,31 @@ main(int argc, char **argv) if (xmldb_setopt(h, "yangspec", yspec) < 0) goto done; if (strcmp(cmd, "get")==0){ - if (argc != 2) + if (argc != 1 && argc != 2) usage(argv0); - if (xmldb_get(h, db, argv[1], &xt, NULL, 0) < 0) + if (xmldb_get(h, db, argc==2?argv[1]:"/", &xt, NULL, 0) < 0) goto done; clicon_xml2file(stdout, xt, 0, 0); + fprintf(stdout, "\n"); } else if (strcmp(cmd, "put")==0){ - if (argc != 4) + if (argc != 3 && argc != 4){ + clicon_err(OE_DB, 0, "Unexpected nr of args: %d", argc); usage(argv0); - if (xml_operation(argv[1], &op) < 0) + } + if (xml_operation(argv[1], &op) < 0){ + clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]); usage(argv0); - if (clicon_xml_parse_str(argv[3], &xt) < 0) + } + if (argc == 4){ + if (clicon_xml_parse_str(argv[3], &xt) < 0) + goto done; + if (xml_rootchild(xt, 0, &xt) < 0) + goto done; + } + if (xmldb_put(h, db, op, argv[2], xt) < 0) goto done; - if (xml_rootchild(xt, 0, &xn) < 0) - goto done; - if (xmldb_put(h, db, op, argv[2], xn) < 0) - goto done; - if (xn) - xml_free(xn); } else if (strcmp(cmd, "copy")==0){ if (argc != 2) @@ -285,9 +289,21 @@ main(int argc, char **argv) if (xmldb_init(h, db) < 0) goto done; } - else + else{ clicon_err(OE_DB, 0, "Unrecognized command: %s", cmd); + usage(argv0); + } + if (xmldb_disconnect(h) < 0) + goto done; + if (xmldb_plugin_unload(h) < 0) + goto done; done: + if (xt) + xml_free(xt); + if (h) + clicon_handle_exit(h); + if (yspec) + yspec_free(yspec); return 0; } diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index 8f73edd4..1b4d3dc8 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -259,12 +259,13 @@ create_keyvalues(cxobj *x, char *keyname) { int retval = -1; + cxobj *xn; cxobj *xb; /* Check if key node exists */ - if ((xb = xml_new_spec(keyname, x, ykey)) == NULL) + if ((xn = xml_new_spec(keyname, x, ykey)) == NULL) goto done; - if ((xb = xml_new("body", xb)) == NULL) + if ((xb = xml_new("body", xn)) == NULL) goto done; xml_type_set(xb, CX_BODY); xml_value_set(xb, arg); @@ -831,6 +832,7 @@ xmldb_put_xkey(struct kv_handle *kh, yang_spec *yspec; char *filename = NULL; + // clicon_log(LOG_WARNING, "%s", __FUNCTION__); if ((yspec = kh->kh_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; @@ -1322,24 +1324,28 @@ kv_put(xmldb_handle xh, if (db_init(dbfilename) < 0) goto done; } - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - /* An api path that is not / */ - if (api_path && strlen(api_path) && strcmp(api_path,"/")){ + if (api_path && strlen(api_path) && strcmp(api_path,"/")){ + // clicon_log(LOG_WARNING, "xmldb_put_restconf_api_path"); + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ if (xmldb_put_restconf_api_path(kh, db, op, api_path, x) < 0) goto done; - continue; } - if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); - goto done; + } + else{ + // clicon_log(LOG_WARNING, "%s", __FUNCTION__); + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ + if ((ys = yang_find_topnode(yspec, xml_name(x))) == NULL){ + clicon_err(OE_UNIX, errno, "No yang node found: %s", xml_name(x)); + goto done; + } + if (put(dbfilename, /* database name */ + x, /* xml root node */ + ys, /* yang statement of xml node */ + op, /* operation, eg merge/delete */ + "" /* aggregate xml key */ + ) < 0) + goto done; } - if (put(dbfilename, /* database name */ - x, /* xml root node */ - ys, /* yang statement of xml node */ - op, /* operation, eg merge/delete */ - "" /* aggregate xml key */ - ) < 0) - goto done; } retval = 0; done: diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 4ee296b8..a185ce75 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -339,12 +339,7 @@ text_get(xmldb_handle xh, goto done; if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0) goto done; - if (xvec0 && xlen0){ - *xvec0 = xvec; - xvec = NULL; - *xlen0 = xlen; - xlen = 0; - } + if (0){ /* No xml_spec(xt) */ if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0) goto done; @@ -356,10 +351,22 @@ text_get(xmldb_handle xh, } if (debug>1) clicon_xml2file(stderr, xt, 0, 1); + if (xvec0 && xlen0){ + *xvec0 = xvec; + xvec = NULL; + *xlen0 = xlen; + xlen = 0; + } *xtop = xt; + xt = NULL; retval = 0; done: - // xml_free(xt); + if (xt) + xml_free(xt); + if (dbfile) + free(dbfile); + if (xvec) + free(xvec); if (fd != -1) close(fd); return retval; @@ -369,10 +376,10 @@ text_get(xmldb_handle xh, * param[in] cvk vector of index keys */ static cxobj * -find_keys(cxobj *xt, - char *name, - cvec *cvk, - char **valvec) +find_keys_vec(cxobj *xt, + char *name, + cvec *cvk, + char **valvec) { cxobj *xi = NULL; int j; @@ -383,29 +390,41 @@ find_keys(cxobj *xt, while ((xi = xml_child_each(xt, xi, CX_ELMNT)) != NULL) if (strcmp(xml_name(xi), name) == 0){ - j = 0; /* All keys must match. */ + j = 0; cvi = NULL; while ((cvi = cvec_each(cvk, cvi)) != NULL) { keyname = cv_string_get(cvi); val = valvec[j++]; if ((body = xml_find_body(xi, keyname)) == NULL) - continue; + break; if (strcmp(body, val)) - continue; + break; } - return xi; + /* All keys must match: loop terminates. */ + if (cvi==NULL) + return xi; } return NULL; } -/*! Create tree from api-path, ie fill in xml tree from the path +/*! Create 'modification' tree from api-path, ie fill in xml tree from the path + * @param[in] api_path api-path expression + * @param[in] xt XML tree. Find api-path (or create) in this tree + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[in] yspec Yang spec + * @param[out] xp Resulting xml tree corresponding to xt + * @param[out] xparp Parent of xp (xp can be NULL) + * @param[out] yp Yang spec matching xp + * @see xmldb_put_xkey for example */ static int -text_create_tree(char *xk, - cxobj *xt, - enum operation_type op, - yang_spec *yspec, - cxobj **xp) +text_create_modtree(char *api_path, + cxobj *xt, + enum operation_type op, + yang_spec *yspec, + cxobj **xp, + cxobj **xparp, + yang_node **yp) { int retval = -1; char **vec = NULL; @@ -417,6 +436,7 @@ text_create_tree(char *xk, yang_stmt *y = NULL; yang_stmt *ykey; cxobj *x = NULL; + cxobj *xpar = NULL; cxobj *xn = NULL; /* new */ cxobj *xb; /* body */ cvec *cvk = NULL; /* vector of index keys */ @@ -427,17 +447,18 @@ text_create_tree(char *xk, char *val2; x = xt; - if (xk == NULL || *xk!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", xk); + xpar = xml_parent(xt); + if (api_path == NULL || *api_path!='/'){ + clicon_err(OE_DB, 0, "Invalid key: %s", api_path); goto done; } - if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) + 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){ - clicon_err(OE_XML, 0, "Malformed key: %s", xk); + clicon_err(OE_XML, 0, "Malformed key: %s", api_path); goto done; } i = 1; @@ -462,10 +483,42 @@ text_create_tree(char *xk, clicon_err(OE_XML, 0, "malformed key, expected '='"); goto done; } - fprintf(stderr, "XXX create =%s\n", restval); + /* See if it exists */ + xn = NULL; + while ((xn = xml_child_each(x, xn, CX_ELMNT)) != NULL) + if (strcmp(name, xml_name(xn)) == 0 && + strcmp(xml_body(xn),restval)==0) + break; + if (xn == NULL){ /* Not found, does not exist */ + switch (op){ + case OP_DELETE: /* not here, should be here */ + clicon_err(OE_XML, 0, "Object to delete does not exist"); + goto done; + break; + case OP_REMOVE: + goto ok; /* not here, no need to remove */ + break; + case OP_CREATE: + if (i==nvec) /* Last, dont create here */ + break; + default: + //XXX create_keyvalues(cxobj *x, + if ((xn = xml_new_spec(y->ys_argument, x, y)) == NULL) + goto done; + // xml_type_set(xn, CX_ELMNT); + if ((xb = xml_new("body", xn)) == NULL) + goto done; + xml_type_set(xb, CX_BODY); + if (xml_value_set(xb, restval) < 0) + goto done; + break; + } + } + xpar = x; x = xn; break; case Y_LIST: + /* Get the yang list key */ if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", __FUNCTION__, y->ys_argument); @@ -489,15 +542,23 @@ text_create_tree(char *xk, } cvi = NULL; /* Check if exists, if not, create */ - if ((xn = find_keys(x, name, cvk, valvec)) == NULL){ - /* create them, bit not if delete op */ - if (op == OP_DELETE || op == OP_REMOVE){ - retval = 0; + if ((xn = find_keys_vec(x, name, cvk, valvec)) == NULL){ + /* create them, but not if delete op */ + switch (op){ + case OP_DELETE: /* not here, should be here */ + clicon_err(OE_XML, 0, "Object to delete does not exist"); goto done; + break; + case OP_REMOVE: + goto ok; /* not here, no need to remove */ + break; + default: + if ((xn = xml_new(name, x)) == NULL) + goto done; + xml_type_set(xn, CX_ELMNT); + break; } - if ((xn = xml_new(name, x)) == NULL) - goto done; - xml_type_set(xn, CX_ELMNT); + xpar = x; x = xn; j = 0; while ((cvi = cvec_each(cvk, cvi)) != NULL) { @@ -506,50 +567,260 @@ text_create_tree(char *xk, if ((xn = xml_new(keyname, x)) == NULL) goto done; xml_type_set(xn, CX_ELMNT); - if ((xb = xml_new(keyname, xn)) == NULL) + if ((xb = xml_new("body", xn)) == NULL) goto done; xml_type_set(xb, CX_BODY); if (xml_value_set(xb, val2) <0) goto done; } } + else{ + xpar = x; + x = xn; + } if (cvk){ cvec_free(cvk); cvk = NULL; } break; - default: /* eg Y_CONTAINER */ - if ((xn = xml_find(x, name)) == NULL){ - /* Already removed */ - if (op == OP_DELETE || op == OP_REMOVE){ - retval = 0; - goto done; - } + default: /* eg Y_CONTAINER, Y_LEAF */ + if ((xn = xml_find(x, name)) == NULL){ + switch (op){ + case OP_DELETE: /* not here, should be here */ + clicon_err(OE_XML, 0, "Object to delete does not exist"); + goto done; + break; + case OP_REMOVE: + goto ok; /* not here, no need to remove */ + break; + case OP_CREATE: + if (i==nvec) /* Last, dont create here */ + break; + default: if ((xn = xml_new(name, x)) == NULL) goto done; xml_type_set(xn, CX_ELMNT); + break; } - x = xn; + } + else{ + if (op==OP_CREATE && i==nvec){ /* here, should not be here */ + clicon_err(OE_XML, 0, "Object to create already exists"); + goto done; + } + } + xpar = x; + x = xn; break; } - } *xp = x; + *xparp = xpar; + *yp = (yang_node*)y; + ok: + retval = 0; + done: + if (vec) + free(vec); + if (valvec) + free(valvec); + return retval; +} + +/*! Check if child with fullmatch exists + * param[in] cvk vector of index keys +*/ +static cxobj * +find_match(cxobj *x0, + cxobj *x1c, + yang_stmt *yc) +{ + cxobj *x0c = NULL; + char *keyname; + cvec *cvk = NULL; + cg_var *cvi; + char *b0; + char *b1; + yang_stmt *ykey; + char *name; + int ok; + + name = xml_name(x1c); + if (yc->ys_keyword != Y_LIST){ + x0c = xml_find(x0, name); + goto done; + } + if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){ + clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", + __FUNCTION__, yc->ys_argument); + goto done; + } + /* The value is a list of keys: [ ]* */ + if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) + goto done; + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { + if (strcmp(xml_name(x0c), name)) + continue; + cvi = NULL; + ok = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + ok = 1; /* if we come here */ + if ((b0 = xml_find_body(x0c, keyname)) == NULL) + break; /* error case */ + if ((b1 = xml_find_body(x1c, keyname)) == NULL) + break; /* error case */ + if (strcmp(b0, b1)) + break; + ok = 2; /* and reaches here for all keynames, x0c is found. */ + } + if (ok == 2) + break; + } + done: + if (cvk) + cvec_free(cvk); + return x0c; +} + +/*! Modify a base tree x0 with x1 with yang spec y according to operation op + * @param[in] x0 Base xml tree + * @param[in] x1 xml tree which modifies base + * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc + * @param[in] y Yang spec corresponding to xml-node x0. NULL if no x0 + * Assume x0 and x1 are same on entry and that y is the spec + * @see put in clixon_keyvalue.c + * XXX: x1 är det som är under x0 + */ +static int +text_modify(cxobj *x0, + cxobj *x0p, + cxobj *x1, + enum operation_type op, + yang_node *y, + yang_spec *yspec) +{ + int retval = -1; + char *opstr; + char *name; + char *cname; /* child name */ + cxobj *x0c; /* base child */ + cxobj *x0b; /* base body */ + cxobj *x1c; /* mod child */ + char *x1bstr; /* mod body string */ + yang_stmt *yc; /* yang child */ + + clicon_debug(1, "%s %s", __FUNCTION__, x0?xml_name(x0):""); + /* Check for operations embedded in tree according to netconf */ + if (x1 && (opstr = xml_find_value(x1, "operation")) != NULL) + if (xml_operation(opstr, &op) < 0) + goto done; + if (x1 == NULL){ + switch(op){ + case OP_REPLACE: + if (x0) + xml_purge(x0); + case OP_CREATE: + case OP_MERGE: + break; + default: + break; + } + } + else { + assert(xml_type(x1) == CX_ELMNT); + name = xml_name(x1); + if (y && (y->yn_keyword == Y_LEAF_LIST || y->yn_keyword == Y_LEAF)){ + x1bstr = xml_body(x1); + switch(op){ + case OP_CREATE: + if (x0){ + clicon_err(OE_XML, 0, "Object to create already exists"); + goto done; + } + /* Fall thru */ + case OP_MERGE: + case OP_REPLACE: + if (x0==NULL){ + if ((x0 = xml_new_spec(name, x0p, y)) == NULL) + goto done; + if ((x0b = xml_new("body", x0)) == NULL) + goto done; + xml_type_set(x0b, CX_BODY); + } + if ((x0b = xml_body_get(x0)) == NULL) + goto done; + if (xml_value_set(x0b, x1bstr) < 0) + goto done; + break; + default: + break; + } /* switch op */ + } /* if LEAF|LEAF_LIST */ + else { /* eg Y_CONTAINER */ + switch(op){ + case OP_CREATE: + /* top-level object is a special case, ie when + * x0 parent is NULL + * or x1 is empty + */ + if ((x0p && x0) || + (x0p==NULL && xml_child_nr(x1) == 0)){ + clicon_err(OE_XML, 0, "Object to create already exists"); + goto done; + } + case OP_REPLACE: + /* top-level object is a special case, ie when + * x0 parent is NULL, + * or x1 is empty + */ + if ((x0p && x0) || + (x0p==NULL && xml_child_nr(x1) == 0)){ + xml_purge(x0); + x0 = NULL; + } + case OP_MERGE: + if (x0==NULL){ + if ((x0 = xml_new_spec(name, x0p, y)) == NULL) + goto done; + } + x1c = NULL; + while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { + cname = xml_name(x1c); + /* XXX This is more than find, if a list keys must match */ + + if (y == NULL) + yc = yang_find_topnode(yspec, cname); /* still NULL for config */ + else + yc = yang_find_syntax(y, cname); + x0c = find_match(x0, x1c, yc); + if (text_modify(x0c, x0, x1c, op, (yang_node*)yc, yspec) < 0) + goto done; + } + break; + default: + break; + } /* CONTAINER switch op */ + } /* else Y_CONTAINER */ + } /* x1 != NULL */ + // ok: retval = 0; done: return retval; } - /*! Modify database provided an xml tree and an operation * * @param[in] xh XMLDB handle * @param[in] db running or candidate - * @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 - * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13]) + * @param[in] api_path According to restconf (Sec 3.5.1.1 in [restconf-draft 13 +]) + * @param[in] xadd 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. @@ -557,17 +828,16 @@ text_create_tree(char *xk, * cxobj *xt; * if (clicon_xml_parse_str("17", &xt) < 0) * err; - * if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0) + * if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0) * err; * @endcode - * @see xmldb_put_xkey for single key */ int text_put(xmldb_handle xh, - char *db, - enum operation_type op, - char *api_path, - cxobj *xadd) + char *db, + enum operation_type op, + char *api_path, + cxobj *xmod) { int retval = -1; struct text_handle *th = handle(xh); @@ -577,12 +847,14 @@ text_put(xmldb_handle xh, cbuf *xpcb = NULL; /* xpath cbuf */ yang_spec *yspec; cxobj *xt = NULL; - cxobj *x = NULL; + cxobj *xbase = NULL; + cxobj *xbasep = NULL; /* parent */ cxobj *xc; - cxobj *xcopy = NULL; + cxobj *xnew = NULL; + yang_node *y = NULL; - if (xadd == NULL || strcmp(xml_name(xadd),"config")){ - clicon_err(OE_XML, 0, "Misformed xml, should start with "); + if ((op==OP_DELETE || op==OP_REMOVE) && xmod){ + clicon_err(OE_XML, 0, "xml tree should be NULL for REMOVE/DELETE"); goto done; } if (text_db2file(th, db, &dbfile) < 0) @@ -615,45 +887,39 @@ text_put(xmldb_handle xh, if (xml_rootchild(xt, 0, &xt) < 0) goto done; } + /* here xt looks like: ... */ /* If xpath find first occurence or api-path (this is where we apply xml) */ if (api_path){ - if (text_create_tree(api_path, xt, op, yspec, &x) < 0) + if (text_create_modtree(api_path, xt, op, yspec, &xbase, &xbasep, &y) < 0) goto done; } - else - x = xt; - assert(x); - /* Here point of where xt is applied to xt is 'x' */ - switch (op){ - case OP_CREATE: - if (x){ - clicon_err(OE_DB, 0, "OP_CREATE: already exists in database"); - goto done; - } - case OP_REPLACE: - while ((xc = xml_child_i(x, 0)) != NULL) - xml_purge(xc); - case OP_MERGE: - /* Loop thru xadd's children */ - xc = NULL; - while ((xc = xml_child_each(xadd, xc, CX_ELMNT)) != NULL){ - if ((xcopy = xml_new("new", x)) == NULL) - goto done; - xml_copy(xc, xcopy); - } - break; - case OP_DELETE: - if (x==NULL){ - clicon_err(OE_DB, 0, "OP_DELETE: does not exist in database"); - goto done; - } - case OP_REMOVE: - while ((xc = xml_child_i(x, 0)) != NULL) - xml_purge(xc); - break; - case OP_NONE: - break; + else{ + xbase = xt; /* defer y since x points to config */ + xbasep = xml_parent(xt); /* NULL */ + assert(strcmp(xml_name(xbase),"config")==0); } + + /* + * Modify base tree x with modification xmod + */ + if (op == OP_DELETE || op == OP_REMOVE){ + /* special case if top-level, dont purge top-level */ + if (xt == xbase){ + xc = NULL; + while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL){ + xml_purge(xc); + xc = NULL; /* reset iterator */ + } + } + else + if (xbase) + xml_purge(xbase); + } + else + if (text_modify(xbase, xbasep, xmod, op, (yang_node*)y, yspec) < 0) + goto done; + // output: + /* Print out top-level xml tree after modification to file */ if ((cb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); goto done; @@ -672,6 +938,8 @@ text_put(xmldb_handle xh, } retval = 0; done: + if (dbfile) + free(dbfile); if (fd != -1) close(fd); if (cb) @@ -680,6 +948,8 @@ text_put(xmldb_handle xh, cbuf_free(xpcb); if (xt) xml_free(xt); + if (xnew) + xml_free(xnew); return retval; } diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 91da69e5..43ab57d9 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -71,6 +71,7 @@ typedef int (xml_applyfn_t)(cxobj *yn, void *arg); /* * Prototypes */ +char *xml_type2str(enum cxobj_type type); char *xml_name(cxobj *xn); int xml_name_set(cxobj *xn, char *name); char *xml_namespace(cxobj *xn); @@ -87,8 +88,6 @@ int xml_value_set(cxobj *xn, char *val); char *xml_value_append(cxobj *xn, char *val); enum cxobj_type xml_type(cxobj *xn); int xml_type_set(cxobj *xn, enum cxobj_type type); -int xml_index(cxobj *xn); -int xml_index_set(cxobj *xn, int index); cg_var *xml_cv_get(cxobj *xn); int xml_cv_set(cxobj *xn, cg_var *cv); @@ -113,6 +112,7 @@ int xml_rm(cxobj *xc); int xml_rootchild(cxobj *xp, int i, cxobj **xcp); char *xml_body(cxobj *xn); +cxobj *xml_body_get(cxobj *xn); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); @@ -127,6 +127,7 @@ int clicon_xml_parse_file(int fd, cxobj **xml_top, char *endtag); int clicon_xml_parse_str(char *str, cxobj **xml_top); int clicon_xml_parse(cxobj **cxtop, char *format, ...); +int xmltree2cbuf(cbuf *cb, cxobj *x, int level); int xml_copy(cxobj *x0, cxobj *x1); cxobj *xml_dup(cxobj *x0); diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index e474cd4f..ce836af7 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -75,7 +75,6 @@ struct xml{ int x_childvec_len; /* length of vector */ enum cxobj_type x_type; /* type of node: element, attribute, body */ char *x_value; /* attribute and body nodes have values */ - int x_index; /* key node, cf sql index */ int _x_vector_i; /* internal use: xml_child_each */ int x_flags; /* Flags according to XML_FLAG_* above */ void *x_spec; /* Pointer to specification, eg yang, by @@ -83,6 +82,36 @@ 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[] = { + {"error", CX_ERROR}, + {"element", CX_ELMNT}, + {"attr", CX_ATTR}, + {"body", CX_BODY}, + {NULL, -1} +}; + +/*! Translate from xml type in enum form to string keyword + * @param[in] type Xml type + * @retval str String keyword + */ +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; +} + /* * Access functions */ @@ -222,7 +251,7 @@ xml_value(cxobj *xn) /*! Set value of xml node, value is copied * @param[in] xn xml node - * @param[in] val new value, null-terminated string, copied by function + * @param[in] val new value, null-terminated string, copied by function * @retval -1 on error with clicon-err set * @retval 0 OK */ @@ -293,33 +322,6 @@ xml_type_set(cxobj *xn, return old; } -/*! Get index/key of xnode - * @param[in] xn xml node - * @retval index of xml node - * index/key is used in case of yang list constructs where one element is key - */ -int -xml_index(cxobj *xn) -{ - return xn->x_index; -} - -/*! Set index of xnode - * @param[in] xn xml node - * @param[in] index new index - * @retval index old index - * index/key is used in case of yang list constructs where one element is key - */ -int -xml_index_set(cxobj *xn, - int index) -{ - int old = xn->x_index; - - xn->x_index = index; - return old; -} - /*! Get cligen variable associated with node * @param[in] xn xml node * @retval cv Cligen variable if set @@ -757,6 +759,16 @@ xml_body(cxobj *xn) return NULL; } +cxobj * +xml_body_get(cxobj *xn) +{ + cxobj *xb = NULL; + + while ((xb = xml_child_each(xn, xb, CX_BODY)) != NULL) + return xb; + return NULL; +} + /*! Find and return the value of a sub xml node * * The value can be of an attribute or body. @@ -881,8 +893,8 @@ xml_print(FILE *f, /*! Print an XML tree structure to a cligen buffer * * @param[in,out] cb Cligen buffer to write to - * @param[in] xn clicon xml tree - * @param[in] level how many spaces to insert before each line + * @param[in] xn Clicon xml tree + * @param[in] level Indentation level * @param[in] prettyprint insert \n and spaces tomake the xml more readable. * * @code @@ -896,47 +908,50 @@ xml_print(FILE *f, */ int clicon_xml2cbuf(cbuf *cb, - cxobj *cx, + cxobj *x, int level, int prettyprint) { cxobj *xc; + char *name; - switch(xml_type(cx)){ + name = xml_name(x); + switch(xml_type(x)){ case CX_BODY: - cprintf(cb, "%s", xml_value(cx)); + cprintf(cb, "%s", xml_value(x)); break; case CX_ATTR: cprintf(cb, " "); - if (xml_namespace(cx)) - cprintf(cb, "%s:", xml_namespace(cx)); - cprintf(cb, "%s=\"%s\"", xml_name(cx), xml_value(cx)); + if (xml_namespace(x)) + cprintf(cb, "%s:", xml_namespace(x)); + cprintf(cb, "%s=\"%s\"", name, xml_value(x)); break; case CX_ELMNT: cprintf(cb, "%*s<", prettyprint?(level*XML_INDENT):0, ""); - if (xml_namespace(cx)) - cprintf(cb, "%s:", xml_namespace(cx)); - cprintf(cb, "%s", xml_name(cx)); + if (xml_namespace(x)) + cprintf(cb, "%s:", xml_namespace(x)); + cprintf(cb, "%s", name); xc = NULL; - while ((xc = xml_child_each(cx, xc, CX_ATTR)) != NULL) + /* print attributes only */ + while ((xc = xml_child_each(x, xc, CX_ATTR)) != NULL) clicon_xml2cbuf(cb, xc, level+1, prettyprint); /* Check for special case instead of */ - if (xml_body(cx)==NULL && xml_child_nr(cx)==0) + if (xml_body(x)==NULL && xml_child_nr(x)==0) cprintf(cb, "/>"); else{ cprintf(cb, ">"); - if (prettyprint && xml_body(cx)==NULL) + if (prettyprint && xml_body(x)==NULL) cprintf(cb, "\n"); xc = NULL; - while ((xc = xml_child_each(cx, xc, -1)) != NULL) { + while ((xc = xml_child_each(x, xc, -1)) != NULL) { if (xml_type(xc) == CX_ATTR) continue; else clicon_xml2cbuf(cb, xc, level+1, prettyprint); } - if (prettyprint && xml_body(cx)==NULL) + if (prettyprint && xml_body(x)==NULL) cprintf(cb, "%*s", level*XML_INDENT, ""); - cprintf(cb, "", xml_name(cx)); + cprintf(cb, "", name); } if (prettyprint) cprintf(cb, "\n"); @@ -975,6 +990,44 @@ xml_parse(char *str, return retval; } +/*! Print actual xml tree datastructures (not xml), mainly for debugging + * @param[in,out] cb Cligen buffer to write to + * @param[in] xn Clicon xml tree + * @param[in] level Indentation level + */ +int +xmltree2cbuf(cbuf *cb, + cxobj *x, + int level) +{ + cxobj *xc; + int i; + + for (i=0; ix_flags) + cprintf(cb, " flags:0x%x", x->x_flags); + if (xml_child_nr(x)) + cprintf(cb, " {"); + cprintf(cb, "\n"); + xc = NULL; + while ((xc = xml_child_each(x, xc, -1)) != NULL) + xmltree2cbuf(cb, xc, level+1); + if (xml_child_nr(x)){ + for (i=0; i" | xml +*/ +#if 0 /* Test program */ + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s.\n\tInput on stdin\n", argv0); + exit(0); +} + +int +main(int argc, char **argv) +{ + cxobj *xt; + cxobj *xc; + cbuf *cb = cbuf_new(); + + if (argc != 1){ + usage(argv[0]); + return 0; + } + if (clicon_xml_parse_file(0, &xt, "") < 0){ + fprintf(stderr, "parsing 2\n"); + return -1; + } + xc = NULL; + while ((xc = xml_child_each(xt, xc, -1)) != NULL) { + xmltree2cbuf(cb, xc, 0); /* dump data structures */ + //clicon_xml2cbuf(cb, xc, 0, 1); /* print xml */ + } + fprintf(stdout, "%s", cbuf_get(cb)); + if (xt) + xml_free(xt); + if (cb) + cbuf_free(cb); + return 0; +} + +#endif /* Test program */ + diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 070f93fb..0ebf7c8e 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -67,7 +67,7 @@ * If init function fails (not found, wrong version, etc) print a log and dont * add it. * @param[in] h CLicon handle - * @param[in] filename Actual filename with path + * @param[in] filename Actual filename including path */ int xmldb_plugin_load(clicon_handle h, @@ -125,7 +125,11 @@ xmldb_plugin_load(clicon_handle h, goto done; } -/*! Unload the xmldb storage plugin */ +/*! Unload the xmldb storage plugin + * @param[in] h Clicon handle + * @retval 0 OK + * @retval -1 Error + */ int xmldb_plugin_unload(clicon_handle h) { @@ -162,9 +166,10 @@ xmldb_plugin_unload(clicon_handle h) return retval; } -/*! Connect to a datastore plugin - * @retval handle Use this handle for other API calls - * @retval NULL Error +/*! Connect to a datastore plugin, allocate handle to be used in API calls + * @param[in] h Clicon handle + * @retval 0 OK + * @retval -1 Error * @note You can do several connects, and have multiple connections to the same * datastore. Note also that the xmldb handle is hidden in the clicon * handle, the clixon user does not need to handle it. Note also that @@ -196,7 +201,8 @@ xmldb_connect(clicon_handle h) /*! Disconnect from a datastore plugin and deallocate handle * @param[in] handle Disconect and deallocate from this handle * @retval 0 OK - */ + * @retval -1 Error + */ int xmldb_disconnect(clicon_handle h) { @@ -225,7 +231,7 @@ xmldb_disconnect(clicon_handle h) } /*! Get value of generic plugin option. Type of value is givenby context - * @param[in] xh XMLDB handle + * @param[in] h Clicon handle * @param[in] optname Option name * @param[out] value Pointer to Value of option * @retval 0 OK @@ -258,7 +264,7 @@ xmldb_getopt(clicon_handle h, } /*! Set value of generic plugin option. Type of value is givenby context - * @param[in] xh XMLDB handle + * @param[in] h Clicon handle * @param[in] optname Option name * @param[in] value Value of option * @retval 0 OK @@ -404,8 +410,9 @@ xmldb_put(clicon_handle h, #if 0 /* XXX DEBUG */ { cbuf *cb = cbuf_new(); - if (clicon_xml2cbuf(cb, xt, 0, 0) < 0) - goto done; + if (xt) + if (clicon_xml2cbuf(cb, xt, 0, 0) < 0) + goto done; clicon_log(LOG_WARNING, "%s: db:%s op:%d api_path:%s xml:%s", __FUNCTION__, db, op, api_path, cbuf_get(cb)); diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 4fe808a8..c89cc4c6 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -241,7 +241,7 @@ xml2cli(FILE *f, !index GT_VARS T !index GT_ALL T */ - bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS && !xml_index(x)); + bool = !leaf(x) || gt == GT_ALL || (gt == GT_VARS); // bool = (!x->xn_index || gt == GT_ALL); if (bool){ if (cbuf_len(cbpre)) @@ -253,12 +253,12 @@ xml2cli(FILE *f, i = 0; while ((xe = xml_child_each(x, xe, -1)) != NULL){ /* Dont call this if it is index and there are other following */ - if (xml_index(xe) && i < nr-1) + if (0 && i < nr-1) ; else if (xml2cli(f, xe, cbuf_get(cbpre), gt) < 0) goto done; - if (xml_index(xe)){ /* assume index is first, otherwise need one more while */ + if (0){ /* assume index is first, otherwise need one more while */ if (gt == GT_ALL) cprintf(cbpre, " %s", xml_name(xe)); cprintf(cbpre, " %s", xml_value(xml_child_i(xe, 0))); diff --git a/lib/src/clixon_xml_parse.y b/lib/src/clixon_xml_parse.y index ede62b8e..113b518f 100644 --- a/lib/src/clixon_xml_parse.y +++ b/lib/src/clixon_xml_parse.y @@ -175,8 +175,10 @@ xml_parse_endslash_post(struct xml_parse_yacc_arg *ya) return 0; } +/*! Called at */ static int -xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name) +xml_parse_bslash1(struct xml_parse_yacc_arg *ya, + char *name) { int retval = -1; cxobj *x = ya->ya_xelement; @@ -199,8 +201,10 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name) ; else{ xc = NULL; - while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) - xml_value_set(xc, ""); /* XXX remove */ + while ((xc = xml_child_each(x, xc, CX_BODY)) != NULL) { + xml_purge(xc); + xc = NULL; /* reset iterator */ + } } } retval = 0; @@ -209,8 +213,11 @@ xml_parse_bslash1(struct xml_parse_yacc_arg *ya, char *name) return retval; } +/*! Called at */ static int -xml_parse_bslash2(struct xml_parse_yacc_arg *ya, char *namespace, char *name) +xml_parse_bslash2(struct xml_parse_yacc_arg *ya, + char *namespace, + char *name) { int retval = -1; cxobj *x = ya->ya_xelement; diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 14a03e84..14cead1f 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -160,6 +160,10 @@ static const struct map_str2int ykmap[] = { {NULL, -1} }; +/*! Create new yang specification + * @retval yspec Free with yspec_free() + * @retval NULL Error + */ yang_spec * yspec_new(void) { @@ -174,6 +178,10 @@ yspec_new(void) return yspec; } +/*! Create new yang node/statement + * @retval ys Free with ys_free() + * @retval NULL Error + */ yang_stmt * ys_new(enum rfc_6020 keyw) { diff --git a/test/test4.sh b/test/test4.sh index 88fa31d6..38875a41 100755 --- a/test/test4.sh +++ b/test/test4.sh @@ -26,6 +26,16 @@ module ietf-ip{ leaf d { type empty; } + container f { + leaf-list e { + type string; + } + } + container h { + leaf j { + type string; + } + } } } EOF @@ -52,6 +62,15 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]> new "netconf get config xpath" expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^125]]>]]>$" +new "netconf edit leaf-list" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "hejhopp]]>]]>" "^]]>]]>$" + +new "netconf get leaf-list" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^hejhopp]]>]]>$" + +new "netconf get leaf-list path" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^hejhopp]]>]]>$" + new "Kill backend" # Check if still alive pid=`pgrep clixon_backend` diff --git a/test/test5.sh b/test/test5.sh index 80137f01..812b2e3f 100755 --- a/test/test5.sh +++ b/test/test5.sh @@ -6,6 +6,7 @@ datastore=datastore_client + cat < /tmp/ietf-ip.yang module ietf-ip{ container x { @@ -24,22 +25,52 @@ module ietf-ip{ leaf d { type empty; } + container f { + leaf-list e { + type string; + } + } + container h { + leaf j { + type string; + } + } } } EOF +db='12first-entry13second-entry23third-entryabcastring' + run(){ name=$1 dir=/tmp/$name if [ ! -d $dir ]; then mkdir $dir fi - rm -rf $dir/* conf="-d candidate -b $dir -p ../datastore/$name/$name.so -y /tmp -m ietf-ip" + echo "conf:$conf" new "datastore $name init" expectfn "$datastore $conf init" "" + new "datastore $name put top" + echo "$datastore $conf put replace '/' $db" + expectfn "$datastore $conf put replace '/' \"$db\"" "" + +return + new "datastore $name get" + expectfn "$datastore $conf get /" $db + + new "datastore $name put rm" + expectfn "$datastore $conf put remove /x/g" + + new "datastore $name put top" + expectfn "$datastore $conf put replace / $db" + + new "datastore $name put del" + expectfn "$datastore $conf put delete /x/g" + +return new "datastore $name get empty" expectfn "$datastore $conf get /" "^$"