diff --git a/.gitignore b/.gitignore index e9ed0e3e..cd4446bc 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ lib/clixon/clixon.h build-root/*.tar.xz build-root/*.rpm build-root/rpmbuild + +test/public +doc/html \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e3837ec..160f6254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,21 +8,40 @@ ### Major New features * NACM extension (RFC8341) - * Move NACM files from backend to lib src dir + * NACM module support (RFC8341 A1+A2) + * Recovery user "_nacm_recovery" added. + * Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user. + * Example user changed adm1 to andy to comply with RFC8341 example * Yang code upgrade (RFC7950) * YANG parser cardinality checked (https://github.com/clicon/clixon/issues/48) * See https://github.com/clicon/clixon/issues/84 + * RPC method input parameters validated + * see https://github.com/clicon/clixon/issues/47 * Support of submodule, include and belongs-to. * Openconfig yang specs parsed: https://github.com/openconfig/public - * Improved unknown handling + * Improved "unknown" handling * Yang load file configure options changed * `CLICON_YANG_DIR` is changed from a single directory to a path of directories * Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list * CLICON_YANG_MAIN_FILE Provides a filename with a single module filename. * CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded. +* Correct XML namespace handling + * XML multiple modules was based on "loose" semantics so that yang modules were found by iterating thorugh namespaces until a match was made. This did not adhere to proper [XML namespace handling](https://www.w3.org/TR/2009/REC-xml-names-20091208), and causes problems with overlapping names and false positives. Below see XML accepted (but wrong), and correct namespace declaration: +``` + # Wrong but accepted + # Correct + + +``` + * To keep old loose semantics set config option CLICON_XML_NS_ITERATE (true by default) + * XML to JSON translator support for mapping xmlns attribute to module name prefix. + * Default namespace is still "urn:ietf:params:xml:ns:netconf:base:1.0" + * See https://github.com/clicon/clixon/issues/49 ### API changes on existing features (you may need to change your code) * Yang parser is stricter (see above) which may break parsing of existing yang specs. +* XML namespace handling is corrected (see above) + * For backward compatibility set config option CLICON_XML_NS_ITERATE * Yang parser functions have changed signatures. Please check the source if you call these functions. * Add `/usr/local/share/clixon` to your configuration file, or corresponding CLICON_DATADIR directory for Clixon system yang files. * Change all @datamodel:tree to @datamodel in all CLI specification files @@ -30,6 +49,8 @@ * For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h ### Minor changes +* Changed all make tags --> make TAGS +* Keyvalue datastore removed (it has been disabled since 3.3.3) * Removed return value ymodp from yang parse functions (eg yang_parse()). * New config option: CLICON_CLI_MODEL_TREENAME defining name of generated syntax tree if CLIXON_CLI_GENMODEL is set. * XML parser conformance to W3 spec @@ -42,6 +63,7 @@ * getopt return value changed from char to int (https://github.com/clicon/clixon/issues/58) ### Known issues +* debug rpc added in example application (should be in clixon-config). ## 3.8.0 (6 Nov 2018) diff --git a/README.md b/README.md index 0389fa17..bc0e7f50 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ Background ========== Clixon was implemented to provide an open-source generic configuration -tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while clixon is a system with configuration database, xml and rest interfaces. Most of the projects using clixon are for embedded network and measuring devices. But Clixon is more generic than that. +tool. The existing [CLIgen](http://www.cligen.se) tool was for command-lines only, while Clixon is a system with configuration database, xml and rest interfaces all defined by Yang. Most of the projects using Clixon are for embedded network and measuring devices. But Clixon can be used for other systems as well due to its modular and pluggable architecture. -Users of clixon currently include: +Users of Clixon currently include: * [Netgate](https://www.netgate.com) * [CloudMon360](http://cloudmon360.com) * [Grideye](http://hagsand.se/grideye) @@ -98,16 +98,16 @@ XML Clixon has its own implementation of XML and XPATH implementation. The standards covered include: -- [XML](https://www.w3.org/TR/2008/REC-xml-20081126) -- [Namespaces](https://www.w3.org/TR/2009/REC-xml-names-20091208) -- [XPATH](https://www.w3.org/TR/xpath-10) +- [XML 1.0](https://www.w3.org/TR/2008/REC-xml-20081126) +- [Namespaces in XML 1.0](https://www.w3.org/TR/2009/REC-xml-names-20091208) +- [XPATH 1.0](https://www.w3.org/TR/xpath-10) Not supported: -- config", NULL) < 0) goto done; goto ok; } else{ + /* yang spec may be set to anyxml by ingress yang check,...*/ + if (xml_spec(xc) != NULL) + xml_spec_set(xc, NULL); + /* Populate XML with Yang spec (why not do this in parser?) + * Maybe validate xml here as in text_modify_top? + */ if (xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xc, CX_ELMNT, xml_non_config_data, &non_config) < 0) @@ -952,13 +958,16 @@ from_client_msg(clicon_handle h, cxobj *xt = NULL; cxobj *x; cxobj *xe; - char *name = NULL; + char *rpc = NULL; + char *module = NULL; char *db; cbuf *cbret = NULL; /* return message */ int pid; int ret; char *username; - char *nacm_mode; + yang_spec *yspec; + yang_stmt *ye; + yang_stmt *ymod; clicon_debug(1, "%s", __FUNCTION__); pid = ce->ce_pid; @@ -974,62 +983,81 @@ from_client_msg(clicon_handle h, goto done; goto reply; } + /* Get yang spec */ + yspec = clicon_dbspec_yang(h); /* XXX maybe move to clicon_msg_decode? */ if ((x = xpath_first(xt, "/rpc")) == NULL){ if (netconf_malformed_message(cbret, "rpc keyword expected")< 0) goto done; goto reply; } + /* Populate incoming XML tree with yang */ + if (xml_spec_populate_rpc(h, x, yspec) < 0) + goto done; + if ((ret = xml_yang_validate_rpc(x)) < 0) + goto done; + if (ret == 0){ + if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + goto done; + goto reply; + } xe = NULL; username = xml_find_value(x, "username"); while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) { - name = xml_name(xe); - clicon_debug(1, "%s name:%s", __FUNCTION__, name); - /* Make NACM access control if enabled as "internal"*/ - nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); - if (nacm_mode && strcmp(nacm_mode, "disabled") != 0){ - if ((ret = nacm_access(h, nacm_mode, name, username, cbret)) < 0) + rpc = xml_name(xe); + if ((ye = xml_spec(xe)) == NULL){ + if (netconf_operation_not_supported(cbret, "protocol", rpc) < 0) goto done; - if (!ret) - goto reply; + goto reply; } - if (strcmp(name, "get-config") == 0){ + if ((ymod = ys_module(ye)) == NULL){ + clicon_err(OE_XML, ENOENT, "rpc yang does not have module"); + goto done; + } + module = ymod->ys_argument; + clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); + /* Make NACM access control if enabled as "internal"*/ + if ((ret = nacm_access(h, rpc, module, username, cbret)) < 0) + goto done; + if (ret == 0) + goto reply; + if (strcmp(rpc, "get-config") == 0){ if (from_client_get_config(h, xe, cbret) <0) goto done; } - else if (strcmp(name, "edit-config") == 0){ + else if (strcmp(rpc, "edit-config") == 0){ if (from_client_edit_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "copy-config") == 0){ + else if (strcmp(rpc, "copy-config") == 0){ if (from_client_copy_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "delete-config") == 0){ + else if (strcmp(rpc, "delete-config") == 0){ if (from_client_delete_config(h, xe, pid, cbret) <0) goto done; } - else if (strcmp(name, "lock") == 0){ + else if (strcmp(rpc, "lock") == 0){ if (from_client_lock(h, xe, pid, cbret) < 0) goto done; } - else if (strcmp(name, "unlock") == 0){ + else if (strcmp(rpc, "unlock") == 0){ if (from_client_unlock(h, xe, pid, cbret) < 0) goto done; } - else if (strcmp(name, "get") == 0){ + else if (strcmp(rpc, "get") == 0){ if (from_client_get(h, xe, cbret) < 0) goto done; } - else if (strcmp(name, "close-session") == 0){ + else if (strcmp(rpc, "close-session") == 0){ xmldb_unlock_all(h, pid); stream_ss_delete_all(h, ce_event_cb, (void*)ce); cprintf(cbret, ""); } - else if (strcmp(name, "kill-session") == 0){ + else if (strcmp(rpc, "kill-session") == 0){ if (from_client_kill_session(h, xe, cbret) < 0) goto done; } - else if (strcmp(name, "validate") == 0){ + else if (strcmp(rpc, "validate") == 0){ if ((db = netconf_db_find(xe, "source")) == NULL){ if (netconf_missing_element(cbret, "protocol", "source", NULL) < 0) goto done; @@ -1038,19 +1066,19 @@ from_client_msg(clicon_handle h, if (from_client_validate(h, db, cbret) < 0) goto done; } - else if (strcmp(name, "commit") == 0){ + else if (strcmp(rpc, "commit") == 0){ if (from_client_commit(h, pid, cbret) < 0) goto done; } - else if (strcmp(name, "discard-changes") == 0){ + else if (strcmp(rpc, "discard-changes") == 0){ if (from_client_discard_changes(h, pid, cbret) < 0) goto done; } - else if (strcmp(name, "create-subscription") == 0){ + else if (strcmp(rpc, "create-subscription") == 0){ if (from_client_create_subscription(h, xe, ce, cbret) < 0) goto done; } - else if (strcmp(name, "debug") == 0){ + else if (strcmp(rpc, "debug") == 0){ if (from_client_debug(h, xe, cbret) < 0) goto done; } @@ -1104,7 +1132,7 @@ from_client_msg(clicon_handle h, /* Sanity: log if clicon_err() is not called ! */ if (retval < 0 && clicon_errno < 0) clicon_log(LOG_NOTICE, "%s: Internal error: No clicon_err call on error (message: %s)", - __FUNCTION__, name?name:""); + __FUNCTION__, rpc?rpc:""); // clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); return retval;// -1 here terminates backend } diff --git a/apps/netconf/netconf_main.c b/apps/netconf/netconf_main.c index cc51d966..55ee628b 100644 --- a/apps/netconf/netconf_main.c +++ b/apps/netconf/netconf_main.c @@ -95,6 +95,10 @@ process_incoming_packet(clicon_handle h, clicon_debug(1, "RECV"); clicon_debug(2, "%s: RCV: \"%s\"", __FUNCTION__, cbuf_get(cb)); + if ((cbret = cbuf_new()) == NULL){ + clicon_err(LOG_ERR, errno, "cbuf_new"); + goto done; + } yspec = clicon_dbspec_yang(h); if ((str0 = strdup(cbuf_get(cb))) == NULL){ clicon_log(LOG_ERR, "%s: strdup: %s", __FUNCTION__, strerror(errno)); @@ -103,19 +107,25 @@ process_incoming_packet(clicon_handle h, str = str0; /* Parse incoming XML message */ if (xml_parse_string(str, yspec, &xreq) < 0){ - if ((cbret = cbuf_new()) == NULL){ - if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) - goto done; - netconf_output(1, cbret, "rpc-error"); - } - else - clicon_log(LOG_ERR, "%s: cbuf_new", __FUNCTION__); free(str0); + if (netconf_operation_failed(cbret, "rpc", "internal error")< 0) + goto done; + netconf_output(1, cbret, "rpc-error"); goto done; } free(str0); - if ((xrpc=xpath_first(xreq, "//rpc")) != NULL) + if ((xrpc=xpath_first(xreq, "//rpc")) != NULL){ + int ret; isrpc++; + if ((ret = xml_yang_validate_rpc(xrpc)) < 0) + goto done; + if (ret == 0){ + if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + goto done; + netconf_output(1, cbret, "rpc-error"); + goto done; + } + } else if (xpath_first(xreq, "//hello") != NULL) ; @@ -134,30 +144,27 @@ process_incoming_packet(clicon_handle h, else{ /* there is a return message in xret */ cxobj *xa, *xa2; assert(xret); - - if ((cbret = cbuf_new()) != NULL){ - if ((xc = xml_child_i(xret,0))!=NULL){ - xa=NULL; - /* Copy message-id attribute from incoming to reply. - * RFC 6241: - * If additional attributes are present in an element, a NETCONF - * peer MUST return them unmodified in the element. This - * includes any "xmlns" attributes. - */ - while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){ - if ((xa2 = xml_dup(xa)) ==NULL) - goto done; - if (xml_addsub(xc, xa2) < 0) - goto done; - } - add_preamble(cbret); - - clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); - add_postamble(cbret); - if (netconf_output(1, cbret, "rpc-reply") < 0){ - cbuf_free(cbret); + if ((xc = xml_child_i(xret,0))!=NULL){ + xa=NULL; + /* Copy message-id attribute from incoming to reply. + * RFC 6241: + * If additional attributes are present in an element, a NETCONF + * peer MUST return them unmodified in the element. This + * includes any "xmlns" attributes. + */ + while ((xa = xml_child_each(xrpc, xa, CX_ATTR)) != NULL){ + if ((xa2 = xml_dup(xa)) ==NULL) goto done; - } + if (xml_addsub(xc, xa2) < 0) + goto done; + } + add_preamble(cbret); + + clicon_xml2cbuf(cbret, xml_child_i(xret,0), 0, 0); + add_postamble(cbret); + if (netconf_output(1, cbret, "rpc-reply") < 0){ + cbuf_free(cbret); + goto done; } } } diff --git a/apps/netconf/netconf_rpc.c b/apps/netconf/netconf_rpc.c index a8627b66..f53b8675 100644 --- a/apps/netconf/netconf_rpc.c +++ b/apps/netconf/netconf_rpc.c @@ -869,6 +869,7 @@ netconf_application_rpc(clicon_handle h, int retval = -1; yang_spec *yspec = NULL; /* application yspec */ yang_stmt *yrpc = NULL; + yang_stmt *ymod = NULL; yang_stmt *yinput; yang_stmt *youtput; cxobj *xoutput; @@ -888,26 +889,33 @@ netconf_application_rpc(clicon_handle h, goto done; } cbuf_reset(cb); - if (xml_namespace(xn) == NULL){ + if (ys_module_by_xml(yspec, xn, &ymod) < 0) + goto done; + if (ymod == NULL){ xml_parse_va(xret, NULL, "" "operation-failed" "rpc" "error" "%s" - "Not recognized" + "Not recognized module" "", xml_name(xn)); goto ok; } - cprintf(cb, "/%s:%s", xml_namespace(xn), xml_name(xn)); - /* Find yang rpc statement, return yang rpc statement if found */ - if (yang_abs_schema_nodeid(yspec, xml_spec(xn), cbuf_get(cb), Y_RPC, &yrpc) < 0) - goto done; + yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn)); + if ((yrpc==NULL) && _CLICON_XML_NS_ITERATE){ + int i; + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((yrpc = yang_find((yang_node*)ymod, Y_RPC, xml_name(xn))) != NULL) + break; + } + } /* Check if found */ if (yrpc != NULL){ /* 1. Check xn arguments with input statement. */ if ((yinput = yang_find((yang_node*)yrpc, Y_INPUT, NULL)) != NULL){ xml_spec_set(xn, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yinput) < 0) + if (xml_apply(xn, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -933,7 +941,7 @@ netconf_application_rpc(clicon_handle h, if ((youtput = yang_find((yang_node*)yrpc, Y_OUTPUT, NULL)) != NULL){ xoutput=xpath_first(*xret, "/"); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xoutput, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -954,7 +962,6 @@ netconf_application_rpc(clicon_handle h, return retval; } - /*! The central netconf rpc dispatcher. Look at first tag and dispach to sub-functions. * Call plugin handler if tag not found. If not handled by any handler, return * error. @@ -985,7 +992,6 @@ netconf_rpc_dispatch(clicon_handle h, if (xml_value_set(xa, username) < 0) goto done; } - xe = NULL; while ((xe = xml_child_each(xn, xe, CX_ELMNT)) != NULL) { if (strcmp(xml_name(xe), "get-config") == 0){ diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index 133f0ed9..42d4d331 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -257,7 +257,6 @@ api_data_get2(clicon_handle h, else{ if (xpath_vec(xret, "%s", &xvec, &xlen, path) < 0) goto done; - clicon_debug(1, "%s: xpath:%s xlen:%d", __FUNCTION__, path, (int)xlen); if (use_xml){ for (i=0; iout, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); ok: @@ -408,9 +407,10 @@ api_data_post(clicon_handle h, yang_spec *yspec; cxobj *xa; cxobj *xret = NULL; - cxobj *xretcom = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ cxobj *xerr = NULL; /* malloced must be freed */ - cxobj *xe; + cxobj *xe; /* dont free */ char *username; clicon_debug(1, "%s api_path:\"%s\" json:\"%s\"", @@ -488,13 +488,22 @@ api_data_post(clicon_handle h, } /* Assume this is validation failed since commit includes validate */ cbuf_reset(cbx); - cprintf(cbx, "", username?username:""); + /* commit/discard should be done automaticaly by the system, therefore + * recovery user is used here (edit-config but not commit may be permitted + by NACM */ + cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ - if (clicon_rpc_discard_changes(h) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; + /* log errors from discard, but ignore */ + if ((xpath_first(xretdis, "//rpc-error")) != NULL) + clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -512,6 +521,8 @@ api_data_post(clicon_handle h, xml_free(xerr); if (xretcom) xml_free(xretcom); + if (xretdis) + xml_free(xretdis); if (xtop) xml_free(xtop); if (xdata) @@ -623,7 +634,8 @@ api_data_put(clicon_handle h, cxobj *xa; char *api_path; cxobj *xret = NULL; - cxobj *xretcom = NULL; + cxobj *xretcom = NULL; /* return from commit */ + cxobj *xretdis = NULL; /* return from discard-changes */ cxobj *xerr = NULL; /* malloced must be freed */ cxobj *xe; char *username; @@ -734,13 +746,23 @@ api_data_put(clicon_handle h, goto ok; } cbuf_reset(cbx); - cprintf(cbx, "", username?username:""); + /* commit/discard should be done automaticaly by the system, therefore + * recovery user is used here (edit-config but not commit may be permitted + by NACM */ + cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){ - if (clicon_rpc_discard_changes(h) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", username?username:""); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; + /* log errors from discard, but ignore */ + if ((xpath_first(xretdis, "//rpc-error")) != NULL) + clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); if (api_return_err(h, r, xe, pretty, use_xml) < 0) goto done; goto ok; @@ -758,6 +780,8 @@ api_data_put(clicon_handle h, xml_free(xerr); if (xretcom) xml_free(xretcom); + if (xretdis) + xml_free(xretdis); if (xtop) xml_free(xtop); if (xdata) @@ -791,7 +815,7 @@ api_data_patch(clicon_handle h, return 0; } -/*! Generic REST DELETE method +/*! Generic REST DELETE method translated to edit-config * @param[in] h CLIXON handle * @param[in] r Fastcgi request handle * @param[in] api_path According to restconf (Sec 3.5.3.1 in rfc8040) @@ -821,7 +845,8 @@ api_data_delete(clicon_handle h, yang_spec *yspec; enum operation_type op = OP_DELETE; cxobj *xret = NULL; - cxobj *xretcom = NULL; + cxobj *xretcom = NULL; /* return from commmit */ + cxobj *xretdis = NULL; /* return from discard */ cxobj *xerr = NULL; char *username; @@ -836,7 +861,6 @@ api_data_delete(clicon_handle h, if ((xtop = xml_new("config", NULL, NULL)) == NULL) goto done; xbot = xtop; - if (api_path && api_path2xml(api_path, yspec, xtop, YC_DATANODE, &xbot, &y) < 0) goto done; if ((xa = xml_new("operation", xbot, NULL)) == NULL) @@ -864,13 +888,22 @@ api_data_delete(clicon_handle h, } /* Assume this is validation failed since commit includes validate */ cbuf_reset(cbx); - cprintf(cbx, "", username?username:""); + /* commit/discard should be done automaticaly by the system, therefore + * recovery user is used here (edit-config but not commit may be permitted + by NACM */ + cprintf(cbx, "", NACM_RECOVERY_USER); cprintf(cbx, ""); if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0) goto done; if ((xerr = xpath_first(xretcom, "//rpc-error")) != NULL){ - if (clicon_rpc_discard_changes(h) < 0) + cbuf_reset(cbx); + cprintf(cbx, "", NACM_RECOVERY_USER); + cprintf(cbx, ""); + if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0) goto done; + /* log errors from discard, but ignore */ + if ((xpath_first(xretdis, "//rpc-error")) != NULL) + clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__); if (api_return_err(h, r, xerr, pretty, use_xml) < 0) goto done; goto ok; @@ -887,6 +920,8 @@ api_data_delete(clicon_handle h, xml_free(xret); if (xretcom) xml_free(xretcom); + if (xretdis) + xml_free(xretdis); if (xtop) xml_free(xtop); clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); @@ -912,6 +947,11 @@ api_data_delete(clicon_handle h, * data-model-specific RPC operations supported by the server. The * server MAY omit this resource if no data-model-specific RPC * operations are advertised. + * From ietf-restconf.yang: + * In XML, the YANG module namespace identifies the module: + * + * In JSON, the YANG module name identifies the module: + * { 'ietf-system:system-restart' : [null] } */ int api_operations_get(clicon_handle h, @@ -926,9 +966,9 @@ api_operations_get(clicon_handle h, { int retval = -1; yang_spec *yspec; - yang_stmt *ym; + yang_stmt *ymod; /* yang module */ yang_stmt *yc; - char *modname; + char *namespace; cbuf *cbx = NULL; cxobj *xt = NULL; @@ -937,18 +977,17 @@ api_operations_get(clicon_handle h, if ((cbx = cbuf_new()) == NULL) goto done; cprintf(cbx, ""); - ym = NULL; - while ((ym = yn_each((yang_node*)yspec, ym)) != NULL) { - modname = ym->ys_argument; + ymod = NULL; + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + namespace = yang_find_mynamespace(ymod); yc = NULL; - while ((yc = yn_each((yang_node*)ym, yc)) != NULL) { + while ((yc = yn_each((yang_node*)ymod, yc)) != NULL) { if (yc->ys_keyword != Y_RPC) continue; - cprintf(cbx, "<%s:%s />", modname, yc->ys_argument); + cprintf(cbx, "<%s xmlns=\"%s\"/>", yc->ys_argument, namespace); } } cprintf(cbx, ""); - clicon_debug(1, "%s xml:%s", __FUNCTION__, cbuf_get(cbx)); if (xml_parse_string(cbuf_get(cbx), yspec, &xt) < 0) goto done; if (xml_rootchild(xt, 0, &xt) < 0) @@ -962,7 +1001,6 @@ api_operations_get(clicon_handle h, if (xml2json_cbuf(cbx, xt, pretty) < 0) goto done; } - clicon_debug(1, "%s ret:%s", __FUNCTION__, cbuf_get(cbx)); FCGX_SetExitStatus(200, r->out); /* OK */ FCGX_FPrintF(r->out, "Content-Type: application/yang-data+%s\r\n", use_xml?"xml":"json"); FCGX_FPrintF(r->out, "\r\n"); @@ -1095,18 +1133,9 @@ api_operations_post(clicon_handle h, if (xml_value_set(xa, username) < 0) goto done; } - /* XXX: something strange for rpc user */ if (api_path2xml(oppath, yspec, xtop, YC_SCHEMANODE, &xbot, &y) < 0) goto done; -#if 0 - { - cbuf *c = cbuf_new(); - clicon_xml2cbuf(c, xtop, 0, 0); - clicon_debug(1, "%s xtop:%s", __FUNCTION__, cbuf_get(c)); - cbuf_free(c); - } -#endif if (data && strlen(data)){ /* Parse input data as json or xml into xml */ if (parse_xml){ @@ -1150,7 +1179,8 @@ api_operations_post(clicon_handle h, } if (yinput){ xml_spec_set(xbot, yinput); /* needed for xml_spec_populate */ - if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yinput) < 0) + /* XXX yinput <-> h ?*/ + if (xml_apply(xbot, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xbot, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -1215,7 +1245,7 @@ api_operations_post(clicon_handle h, #endif cbuf_reset(cbx); xml_spec_set(xoutput, youtput); /* needed for xml_spec_populate */ - if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, youtput) < 0) + if (xml_apply(xoutput, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; if (xml_apply(xoutput, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, NULL) < 0) @@ -1235,9 +1265,6 @@ api_operations_post(clicon_handle h, else if (xml2json_cbuf(cbx, xoutput, pretty) < 0) goto done; -#if 1 - clicon_debug(1, "%s cbx:%s", __FUNCTION__, cbuf_get(cbx)); -#endif FCGX_FPrintF(r->out, "%s", cbx?cbuf_get(cbx):""); FCGX_FPrintF(r->out, "\r\n\r\n"); } diff --git a/datastore/Makefile.in b/datastore/Makefile.in index 98bc8a0e..6943dec5 100644 --- a/datastore/Makefile.in +++ b/datastore/Makefile.in @@ -49,7 +49,6 @@ INSTALLFLAGS = @INSTALLFLAGS@ LDFLAGS = @LDFLAGS@ LIBS = @LIBS@ with_restconf = @with_restconf@ -with_keyvalue = @with_keyvalue@ SH_SUFFIX = @SH_SUFFIX@ CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@ @@ -117,5 +116,5 @@ distclean: clean for i in $(SUBDIRS); \ do (cd $$i; $(MAKE) $(MFLAGS) $@); done -tags: +TAGS: find $(srcdir) -name '*.[chyl]' -print | etags - diff --git a/datastore/README.md b/datastore/README.md index a3283c6e..8f000d88 100644 --- a/datastore/README.md +++ b/datastore/README.md @@ -2,8 +2,7 @@ The Clixon datastore is a stand-alone XML based datastore. The idea is to be able to use different datastores backends with the same -API. There is currently a key-value plugin based on qdbm and a plain -text-file datastore. +API. There is currently only a plain text-file datastore. The datastore is primarily designed to be used by Clixon but can be used separately. @@ -38,7 +37,7 @@ int xmldb_create(clicon_handle h, char *db); To use the API, a client needs the following: - A clicon handle. -- A datastore plugin, such as a text.so or keyvalue.so. These are normally built and installed at Clixon make. +- A datastore plugin, such as a text.so. These are normally built and installed at Clixon make. - A directory where to store databases - A yang specification. This needs to be parsed using the Clixon yang_parse() method. diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index 5722d497..63f1e7a0 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -31,14 +31,6 @@ ***** END LICENSE BLOCK ***** - * Examples: - -./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip get / - -sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge /interfaces/interface=eth66 'eth66' - -sudo ./datastore_client -d candidate -b /usr/local/var/example -p /home/olof/src/clixon/datastore/keyvalue/keyvalue.so -y /usr/local/share/example/yang -m ietf-ip put merge / 'eth0true' - */ #ifdef HAVE_CONFIG_H @@ -247,6 +239,7 @@ main(int argc, char **argv) clicon_err(OE_DB, 0, "Unrecognized operation: %s", argv[1]); usage(argv0); } + _CLICON_XML_NS_ITERATE = 1; if (xml_parse_string(argv[2], NULL, &xt) < 0) goto done; if (xml_rootchild(xt, 0, &xt) < 0) diff --git a/datastore/keyvalue/Makefile.in b/datastore/keyvalue/Makefile.in deleted file mode 100644 index 08fc997f..00000000 --- a/datastore/keyvalue/Makefile.in +++ /dev/null @@ -1,98 +0,0 @@ -# -# ***** BEGIN LICENSE BLOCK ***** -# -# Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren -# -# 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 ***** -# -VPATH = @srcdir@ -prefix = @prefix@ -datarootdir = @datarootdir@ -srcdir = @srcdir@ -top_srcdir = @top_srcdir@ -exec_prefix = @exec_prefix@ -bindir = @bindir@ -libdir = @libdir@ -dbdir = @prefix@/db -mandir = @mandir@ -libexecdir = @libexecdir@ -localstatedir = @localstatedir@ -sysconfdir = @sysconfdir@ - -VPATH = @srcdir@ -CC = @CC@ -CFLAGS = @CFLAGS@ -rdynamic -fPIC -INSTALLFLAGS = @INSTALLFLAGS@ -LDFLAGS = @LDFLAGS@ -LIBS = @LIBS@ -DATASTORE = keyvalue -CPPFLAGS = @CPPFLAGS@ - -INCLUDES = -I. -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@ - -PLUGIN = $(DATASTORE).so - -SRC = clixon_keyvalue.c clixon_qdb.c clixon_chunk.c - -OBJS = $(SRC:.c=.o) - -all: $(PLUGIN) - - -$(PLUGIN): $(SRC) - $(CC) $(CPPFLAGS) $(INCLUDES) $(CFLAGS) $(LDFLAGS) -shared -o $@ -lc $^ $(LIBS) - -clean: - rm -f $(PLUGIN) $(OBJS) *.core - -distclean: clean - rm -f Makefile *~ .depend - -.SUFFIXES: -.SUFFIXES: .c .o - -.c.o: $(SRC) - $(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $< - -install: $(PLUGIN) - install -d -m 0755 $(DESTDIR)$(libdir)/xmldb - install -m 0644 $(INSTALLFLAGS) $(PLUGIN) $(DESTDIR)$(libdir)/xmldb - -install-include: - -uninstall: - rm -rf $(DESTDIR)$(libdir)/xmldb/$(PLUGIN) - -TAGS: - find . -name '*.[chyl]' -print | etags - - -depend: - $(CC) $(DEPENDFLAGS) @DEFS@ $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend - -#include .depend - diff --git a/datastore/keyvalue/clixon_chunk.c b/datastore/keyvalue/clixon_chunk.c deleted file mode 100644 index 80db76da..00000000 --- a/datastore/keyvalue/clixon_chunk.c +++ /dev/null @@ -1,794 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - */ -/* Error handling: dont use clicon_err, treat as unix system calls. That is, - ensure errno is set and return -1/NULL */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* clicon */ -#include - -/* clicon */ -#include - -#include "clixon_chunk.h" - -/* - * The chunk head array for the predefined chunk sizes. - */ -static chunk_head_t chunk_heads[CHUNK_HEADS]; - -/* - * Did we initialize the chunk heads yet? - */ -static int chunk_initialized = 0; - - -/* - * The pagesize of this system - */ -static int chunk_pagesz; - - -/* - * List of chunk groups - */ -static chunk_group_t *chunk_grp; - -/* - * Hack to tell unchunk() not to remove chunk_group if empty - */ -static int dont_unchunk_group; - - -/* - * Initialize chunk library - */ -static void -chunk_initialize () -{ - int pgs; - register int idx; - - chunk_pagesz = getpagesize(); - - bzero (&chunk_heads, sizeof(chunk_heads)); - - for (idx = 0; idx < CHUNK_HEADS; idx++) { - chunk_head_t *chead = &chunk_heads[idx]; - - - /* - * Calculate the size of a block - */ - pgs = (0x01lu << (CHUNK_BASE + idx)) / chunk_pagesz; - if (pgs == 0) - pgs = 1; - chead->ch_blksz = pgs * chunk_pagesz; - - - /* - * Chunks per block is 1 for all size above a page. For sizes - * (including the chunk header) less than a page it's as many - * as fits - */ - chead->ch_nchkperblk = chead->ch_blksz / (0x01lu << (CHUNK_BASE + idx)); - - - /* - * Size of each chunk is: - * (size + chnkhdr) * ncnkperblk = blksiz - blkhdr - */ - chead->ch_size = - (chead->ch_blksz / chead->ch_nchkperblk) - - sizeof(chunk_t); - - } - - /* Zero misc variables */ - chunk_grp = NULL; - dont_unchunk_group = 0; - - chunk_initialized = 1; -} - - -/* - * chunk_new_block() - Allocate new block, initialize it and it's chunks. - */ -static int -chunk_new_block (chunk_head_t *chead) -{ - register int idx; - register char *c; - chunk_block_t *blk; - chunk_t *cnk; - - /* Map block header mem */ - blk = (chunk_block_t *) - mmap(NULL, sizeof(chunk_block_t), - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (blk == MAP_FAILED) - return -1; - memset((void *)blk, 0, sizeof(*blk)); - - /* Allocate chunk block */ - blk->cb_blk = (void *) - mmap(NULL, chead->ch_blksz, - PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (blk->cb_blk == MAP_FAILED) { - munmap(blk, chead->ch_blksz); - return -1; - } - memset(blk->cb_blk, 0, chead->ch_blksz); - - - /* Initialize chunk header */ - blk->cb_head = chead; - INSQ(blk, chead->ch_blks); - chead->ch_nblks++; - - /* Initialize chunks */ - c = ((char *)blk->cb_blk); - for (idx = 0; idx < chead->ch_nchkperblk; idx++) { - - cnk = (chunk_t *)c; - cnk->c_blk = blk; - INSQ(cnk, chead->ch_free); - chead->ch_nfree++; - - c += (chead->ch_size + sizeof(chunk_t)); - } - - - return 0; -} - -/* - * chunk_release_block() - Unqueue a block, it's chunks and free mem - */ -static void -chunk_release_block(chunk_block_t *cblk) -{ - int idx; - char *c; - chunk_t *cnk; - chunk_head_t *chead; - - - chead = cblk->cb_head; - - /* - * Dequeue block - */ - DELQ(cblk, chead->ch_blks, chunk_block_t *); - chead->ch_nblks--; - - /* - * Dequeue all chunks in the block - */ - c = (char *)cblk->cb_blk; - for (idx = 0; idx < chead->ch_nchkperblk; idx++) { - - cnk = (chunk_t *)c; - DELQ(cnk, chead->ch_free, chunk_t *); - chead->ch_nfree--; - - c += (chead->ch_size + sizeof(chunk_t)); - } - - /* - * Free block - */ - munmap((void *)cblk->cb_blk, chead->ch_blksz); - munmap((void *)cblk, sizeof(*cblk)); -} - - - -/* - * chunk_alloc() - Map new chunk of memory - */ -static void * -chunk_alloc(size_t len) -{ - register int idx; - chunk_head_t *chead; - chunk_t *cnk; - - - if (!len) - return (void *)NULL; - - - - /* Find sufficient sized block head */ - for (idx = 0; idx < CHUNK_HEADS; idx++) - if (chunk_heads[idx].ch_size >= len) - break; - - /* Too large chunk? */ - if (idx >= CHUNK_HEADS) { - errno = ENOMEM; - return (void *)NULL; - } - - chead = &chunk_heads[idx]; - - - /* Get new block if necessary */ - if (!chead->ch_nfree) - if (chunk_new_block(chead)) - return (void *)NULL; - - - /* Move a free chunk to the in-use list */ - cnk = chead->ch_free; - DELQ(cnk, chead->ch_free, chunk_t *); - chead->ch_nfree--; - INSQ(cnk, chead->ch_cnks); - /* Add reference to the corresponding block */ - cnk->c_blk->cb_ref++; - -#ifdef CHUNK_DIAG - /* Clear diag info */ - bzero((void *)&cnk->c_diag, sizeof(cnk->c_diag)); -#endif /* CHUNK_DIAG */ - - /* Return pointer to first byte after the chunk header */ - return (void *) (cnk + 1); -} - - -/* - * chunk() - Map new chunk of memory in group - */ -void * -#ifdef CHUNK_DIAG -_chunk(size_t len, const char *name, const char *file, int line) -#else -chunk(size_t len, const char *name) -#endif -{ - int newgrp = 0; - void *ptr = NULL; - chunk_t *cnk; - chunk_group_t *tmp; - chunk_group_t *grp = NULL; - chunk_grpent_t *ent = NULL; - - /* Make sure chunk_heads are initialized */ - if (!chunk_initialized) - chunk_initialize(); - - if (!len) - return (void *)NULL; - - /* Get actual chunk - */ - ptr = chunk_alloc(len); - if (!ptr) - goto error; - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - -#ifdef CHUNK_DIAG - /* Store who reuested us - */ - cnk->c_diag.cd_file = file; - cnk->c_diag.cd_line = line; -#endif /* CHUNK_DIAG */ - - /* No name given. Get an ungrouped chunk - */ - if (!name) - return ptr; - - - /* Try to find already existing entry - */ - if (chunk_grp) { - tmp = chunk_grp; - do { - if (!strcmp(tmp->cg_name, name)) { - grp = tmp; - break; - } - - tmp = NEXTQ(chunk_group_t *, tmp); - - } while (tmp != chunk_grp); - } - - /* New group. - */ - if ( !grp ) { - - grp = (chunk_group_t *) chunk_alloc(sizeof(chunk_group_t)); - if (!grp) - goto error; - - bzero(grp, sizeof(chunk_group_t)); - - grp->cg_name = (char *) chunk_alloc(strlen(name) + 1); - if (!grp->cg_name) - goto error; - bcopy(name, grp->cg_name, strlen(name)+1); - newgrp = 1; - } - - - /* Get new entry. - */ - ent = (chunk_grpent_t *) chunk_alloc(sizeof(chunk_grpent_t)); - if (!ent) - goto error; - bzero(ent, sizeof(chunk_grpent_t)); - - /* Now put everything together - */ - cnk->c_grpent = ent; - - ent->ce_cnk = cnk; - ent->ce_grp = grp; - - INSQ(ent, grp->cg_ent); - if (newgrp) - INSQ(grp, chunk_grp); - - return (ptr); - - error: - if (grp && newgrp) { - if (grp->cg_name) - unchunk(grp->cg_name); - unchunk(grp); - } - if (ent) - unchunk(ent); - if (ptr) - unchunk(ptr); - - return (void *) NULL; -} - - -/* - * rechunk() - Resize previously allocated chunk. - */ -void * -#ifdef CHUNK_DIAG -_rechunk(void *ptr, size_t len, const char *name, const char *file, int line) -#else -rechunk(void *ptr, size_t len, const char *name) -#endif -{ - int idx; - void *new; - chunk_t *cnk; - chunk_t *newcnk; - chunk_head_t *chead; - chunk_head_t *newchead; - - - /* No previoud chunk, get new - */ - if (!ptr) { -#ifdef CHUNK_DIAG - return _chunk(len, name, file, line); -#else - return chunk(len, name); -#endif - } - - /* Zero length, free chunk - */ - if (len == 0) { - unchunk(ptr); - return (void *) NULL; - } - - /* Rewind pointer to beginning of chunk header - */ - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - chead = cnk->c_blk->cb_head; - - /* Find sufficient sized block head - */ - for (idx = 0; idx < CHUNK_HEADS; idx++) - if (chunk_heads[idx].ch_size >= len) - break; - /* Too large chunk? */ - if (idx >= CHUNK_HEADS) { - errno = ENOMEM; - return (void *)NULL; - } - - /* Check if chunk size remains unchanged - */ - if (chunk_heads[idx].ch_size == chead->ch_size) - return (ptr); - - /* Get new chunk - */ -#ifdef CHUNK_DIAG - new = _chunk(len, name, file, line); -#else - new = chunk(len, name); -#endif - if (!new) - return (void *) NULL; - newcnk = (chunk_t *) (((char *)new) - sizeof(chunk_t)); - newchead = newcnk->c_blk->cb_head; - - /* Copy contents to new chunk - */ - bcopy(ptr, new, MIN(newchead->ch_size, chead->ch_size)); - - /* Free old chunk - */ - unchunk(ptr); - - - return (new); -} - -/* - * unchunk() - Release chunk - */ -void -unchunk(void *ptr) -{ - chunk_t *cnk; - chunk_head_t *chead; - chunk_block_t *cblk; - chunk_grpent_t *ent; - chunk_group_t *grp; - - if (!chunk_initialized) - return; - - /* Rewind pointer to beginning of chunk header - */ - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - cblk = cnk->c_blk; - chead = cblk->cb_head; - - /* Move chunk back to free list - */ - DELQ(cnk, chead->ch_cnks, chunk_t *); - INSQ(cnk, chead->ch_free); - chead->ch_nfree++; - - /* If chunk is grouped, remove from group. - */ - ent = cnk->c_grpent; - if (ent) { - grp = ent->ce_grp; - DELQ(ent, grp->cg_ent, chunk_grpent_t *); - unchunk(ent); - cnk->c_grpent = NULL; - - /* Group empty? */ - if (!dont_unchunk_group && !grp->cg_ent) { - DELQ(grp, chunk_grp, chunk_group_t *); - unchunk(grp->cg_name); - unchunk(grp); - } - } - - /* Check block refs is nil, if so free it - */ - cblk->cb_ref--; - if (cblk->cb_ref == 0) - chunk_release_block (cblk); -} - - -/* - * unchunk_group() - Release all group chunks. - */ -void -unchunk_group(const char *name) -{ - chunk_group_t *tmp; - chunk_group_t *grp = NULL; - chunk_t *cnk; - - if (!chunk_initialized) - return; - - /* Try to find already existing entry - */ - if (chunk_grp) { - tmp = chunk_grp; - do { - if (!strcmp(tmp->cg_name, name)) { - grp = tmp; - break; - } - - tmp = NEXTQ(chunk_group_t *, tmp); - - } while (tmp != chunk_grp); - } - if (!grp) - return; - - - /* Walk through all chunks in group an free them - */ - dont_unchunk_group = 1; - while (grp->cg_ent) { - cnk = grp->cg_ent->ce_cnk; - unchunk((chunk_t *)(((char *)cnk) + sizeof(chunk_t))); - } - dont_unchunk_group = 0; - - - /* Remove group from list and free it - */ - DELQ(grp, chunk_grp, chunk_group_t *); - unchunk(grp->cg_name); - unchunk(grp); -} - -/* - * chunkdup() - Copy block of data to a new chunk of memory in group - */ -void * -#ifdef CHUNK_DIAG -_chunkdup(const void *ptr, size_t len, const char *name, const char *file, int line) -#else -chunkdup(const void *ptr, size_t len, const char *name) -#endif -{ - void *new; - - /* No input data or no length - */ - if (!ptr || len <= 0) - return (void *)NULL; - - /* Get new chunk - */ -#ifdef CHUNK_DIAG - new = _chunk(len, name, file, line); -#else - new = chunk(len, name); -#endif - if (!new) - return (void *)NULL; - - /* Copy data to new chunk - */ - memcpy(new, ptr, len); - - return (new); -} - -/* - * chunksize() - Return size of memory chunk. - */ -size_t -chunksize(void *ptr) -{ - chunk_t *cnk; - chunk_head_t *chead; - chunk_block_t *cblk; - - if (!chunk_initialized) - return -1; - - /* Rewind pointer to beginning of chunk header - */ - cnk = (chunk_t *) (((char *)ptr) - sizeof(chunk_t)); - cblk = cnk->c_blk; - chead = cblk->cb_head; - - return chead->ch_size; -} - -/* - * chunk_strncat() - Concatenate 'n' characters to a chunk allocated string. If - * 'n' is zero, do the whole src string. - * - */ -char * -#ifdef CHUNK_DIAG -_chunk_strncat(const char *dst, const char *src, size_t n, const char *name, - const char *file, int line) -#else -chunk_strncat(const char *dst, const char *src, size_t n, const char *name) -#endif -{ - size_t len; - char *new; - void *ptr = (void *)dst; - - if (n == 0) /* zero length means cat whole string */ - n = strlen(src); - len = (dst ? strlen(dst) : 0) + n + 1; -#ifdef CHUNK_DIAG - ptr = _rechunk(ptr, len, name, file, line); -#else - ptr = rechunk(ptr, len, name); -#endif - if (ptr == NULL) - return NULL; - - new = (char *)ptr; - new += strlen(new); - while (n-- > 0 && *src) - *new++ = *src++; - *new = '\0'; - - return (char *)ptr; -} - -/* - * chunk_sprintf() - Format string into new chunk. - */ -char * -#ifdef CHUNK_DIAG -_chunk_sprintf(const char *name, const char *file, - int line, const char *fmt, ...) -#else -chunk_sprintf(const char *name, char *fmt, ...) -#endif -{ - size_t len; - char *str; - va_list args; - - /* Calculate formatted string length */ - va_start(args, fmt); - len = vsnprintf(NULL, 0, fmt, args) + 1; - va_end(args); - - /* get chunk */ -#ifdef CHUNK_DIAG - str = _chunk(len, name, file, line); -#else - str = chunk(len, name); -#endif - if (str == NULL) - return NULL; - - /* Format string */ - va_start(args, fmt); - len = vsnprintf(str, len, fmt, args); - va_end(args); - - return str; -} - -#ifdef CHUNK_DIAG -/* - * chunk_check() - Report all non-freed chunk for given group (if any) - */ -void -chunk_check(FILE *fout, const char *name) -{ - int idx; - chunk_t *cnk; - chunk_group_t *tmp; - chunk_group_t *grp = NULL; - chunk_grpent_t *ent; - - if (!chunk_initialized) - return; - /* No name given, walk through everything - */ - if (name == (const char *)NULL) { - for (idx = 0; idx < CHUNK_HEADS; idx++) { - chunk_head_t *chead = &chunk_heads[idx]; - cnk = chead->ch_cnks; - if (cnk == (chunk_t *)NULL) - continue; - - do { - - /* If no file name it's an internal chunk */ - if (cnk->c_diag.cd_file) - clicon_debug(0, - "%s:%d,\t%zu bytes (%p), group \"%s\"\n", - cnk->c_diag.cd_file, - cnk->c_diag.cd_line, - cnk->c_blk->cb_head->ch_size, - (cnk +1), - cnk->c_grpent ? - cnk->c_grpent->ce_grp->cg_name : - "NULL"); - - cnk = NEXTQ(chunk_t *, cnk); - - } while (cnk != chead->ch_cnks); - } - } - - /* Walk through group - */ - else { - - - /* Try to find already existing entry - */ - if (chunk_grp) { - tmp = chunk_grp; - do { - if (!strcmp(tmp->cg_name, name)) { - grp = tmp; - break; - } - - tmp = NEXTQ(chunk_group_t *, tmp); - - } while (tmp != chunk_grp); - } - if (!grp) - return; - - ent = grp->cg_ent; - do { - cnk = ent->ce_cnk; - - fprintf(fout ? fout : stdout, - "%s:%d,\t%zu bytes (%p), group \"%s\"\n", - cnk->c_diag.cd_file, - cnk->c_diag.cd_line, - cnk->c_blk->cb_head->ch_size, - (cnk +1), - cnk->c_grpent ? - cnk->c_grpent->ce_grp->cg_name : - "NULL"); - - ent = NEXTQ(chunk_grpent_t *, ent); - } while (ent != grp->cg_ent); - } -} -#else /* CHUNK_DIAG */ -void -chunk_check(FILE *fout, const char *name) -{ -} -#endif /* CHUNK_DIAG */ diff --git a/datastore/keyvalue/clixon_chunk.h b/datastore/keyvalue/clixon_chunk.h deleted file mode 100644 index 81a8f18b..00000000 --- a/datastore/keyvalue/clixon_chunk.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - * - */ - -#ifndef _CLIXON_CHUNK_H_ -#define _CLIXON_CHUNK_H_ - - -/* - * Compile with chunk diagnostics. XXX Should be in Makefile.in ?? - */ -#undef CHUNK_DIAG - -/* - * Base number of bits to shift getting the size of a chunk_head. - */ -#define CHUNK_BASE 6 - -/* - * Number of predefined chunk sizes. I.e. the number of chunk heads in the - * chunk_heads vector. - */ -#define CHUNK_HEADS (32 - CHUNK_BASE) - - -#ifdef CHUNK_DIAG -/* - * Chunk diagnostics - */ -typedef struct _chunk_diag_t { - const char *cd_file; /* File which requested chunk */ - - int cd_line; /* Line in requesting file */ - -} chunk_diag_t; -#endif /* CHUNK_DIAG */ - -/* - * The block header. - */ -struct _chunk_head_t; -typedef struct _chunk_head_t chunk_head_t; - -typedef struct _chunk_block_t { - qelem_t cb_qelem; /* Circular queue of blocks */ - - chunk_head_t *cb_head; /* The chunk head I belong to */ - - void *cb_blk; /* Allocated memory block */ - - uint16_t cb_ref; /* Number of used chunks of block */ - -} chunk_block_t; - - -/* - * The chunk header. - */ -struct _chunk_grpent_t; -typedef struct _chunk_grpent_t chunk_grpent_t; -typedef struct _chunk_t { - qelem_t c_qelem; /* Circular queue of chunks */ - - chunk_block_t *c_blk; /* The block I belong to */ - -#ifdef CHUNK_DIAG - chunk_diag_t c_diag; /* The diagnostics structure */ -#endif /* CHUNK_DIAG */ - - chunk_grpent_t *c_grpent; -} chunk_t; - -/* - * The head of a chunk size. Each predefined size has it's own head keeping - * track of all blocks and chunks for the size. - */ -struct _chunk_head_t { - size_t ch_size; /* Chunk size */ - int ch_nchkperblk; /* Number pf chunks per block */ - - size_t ch_blksz; /* Size of a block */ - int ch_nblks; /* Number of allocated blocks */ - - chunk_block_t *ch_blks; /* Circular list of blocks */ - - chunk_t *ch_cnks; /* Circular list of chunks in use */ - - size_t ch_nfree; /* Number of free chunks */ - chunk_t *ch_free; /* Circular list of free chunks */ - -}; - - -/* - * The chunk group structure. - */ -typedef struct _chunk_group_t { - qelem_t cg_qelem; /* List of chunk groups */ - - char *cg_name; /* Name of group */ - - chunk_grpent_t *cg_ent; /* List of chunks in the group */ - -} chunk_group_t; - - -/* - * The chunk group entry structure. - */ -struct _chunk_grpent_t { - qelem_t ce_qelem; /* Circular list of entries */ - - chunk_group_t *ce_grp; /* The group I belong to */ - - chunk_t *ce_cnk; /* Pointer to the chunk */ -}; - -/* - * Public function declarations - */ -#ifdef CHUNK_DIAG -void *_chunk (size_t, const char *, const char *, int); -#define chunk(siz,label) _chunk((siz),(label),__FILE__,__LINE__) -void *_rechunk (void *, size_t, const char *, const char *, int); -#define rechunk(ptr,siz,label) _rechunk((ptr),(siz),(label),__FILE__,__LINE__) -void *_chunkdup (const void *, size_t, const char *, const char *, int); -#define chunkdup(ptr,siz,label) _chunkdup((ptr),(siz),(label),__FILE__,__LINE__) -char *_chunk_strncat (const char *, const char *, size_t, const char *, const char *, int); -#define chunk_strncat(str,new,n,label) _chunk_strncat((str),(new),(n),(label),__FILE__,__LINE__) -char *_chunk_sprintf (const char *, const char *, int, const char *, ...); -#define chunk_sprintf(label,fmt,...) _chunk_sprintf((label),__FILE__,__LINE__,(fmt),__VA_ARGS__) -#else /* CHUNK_DIAG */ -void *chunk (size_t, const char *); -void *rechunk (void *, size_t, const char *); -void *chunkdup (const void *, size_t, const char *); -char *chunk_strncat (const char *, const char *, size_t, const char *); -char *chunk_sprintf (const char *, char *, ...); -#endif /* CHUNK_DIAG */ -void unchunk (void *); -void unchunk_group (const char *); -void chunk_check (FILE *, const char *); -size_t chunksize (void *); - - -#endif /* _CLIXON_CHUNK_H_ */ diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c deleted file mode 100644 index 39a11134..00000000 --- a/datastore/keyvalue/clixon_keyvalue.c +++ /dev/null @@ -1,1130 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - */ -/* - * An xml database consists of key-value pairs for xml-trees. - * Each node in an xml-tree has a key and an optional value. - * The key (xmlkey) is constructed from the xml node name concatenated - * with its ancestors and any eventual list keys. - * A xmlkeyfmt is a help-structure used when accessing the XML database. - * It consists of an xmlkey but with the key fields replaced with wild-chars(%s) - * Example: /aaa/bbb/%s/%s/ccc - * Such an xmlkeyfmt can be obtained from a yang-statement by following - * its ancestors to the root module. If one of the ancestors is a list, - * a wildchar (%s) is inserted for each key. - * These xmlkeyfmt keys are saved and used in cli callbacks such as when - * modifying syntax (eg cli_merge/cli_delete) or when completing for sub-symbols - * In this case, the variables are set and the wildcards can be instantiated. - * An xml tree can then be formed that can be used to the xmldb_get() or - * xmldb_put() functions. - * The relations between the functions and formats are as follows: - * - * +-----------------+ +-----------------+ - * | yang-stmt | yang2api_path_fmt | api_path_fmt | api_path_fmt2xpath - * | list aa,leaf k | ----------------->| /aa=%s |----------------> - * +-----------------+ +-----------------+ - * | - * | api_path_fmt2api_path - * | k=17 - * v - * +-------------------+ +-----------------+ - * | xml-tree/cxobj | xmlkey2xml |api_path RFC3986| - * | 17| <------------- | /aa=17 | - * +-------------------+ +-----------------+ - * - * Alternative for xmlkeyfmt would be eg: - * RESTCONF: /interfaces/interface=%s/ipv4/address/ip=%s (used) - * XPATH: /interfaces/interface[name='%s']/ipv4/address/[ip'=%s'] - * - * Paths through the code (for coverage) - * cli_callback_generate +----------------+ - * cli_expand_var_generate | yang2api_path_fmt | - * yang -------------> | | - * +----------------+ - * dependency on clixon handle: - * clixon_xmldb_dir() - * clicon_dbspec_yang(h) - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" /* generated by config & autoconf */ -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* cligen */ -#include - -/* clicon */ -#include - -#include "clixon_chunk.h" -#include "clixon_qdb.h" -#include "clixon_keyvalue.h" - -#define handle(xh) (assert(kv_handle_check(xh)==0),(struct kv_handle *)(xh)) - -/* Magic to ensure plugin sanity. */ -#define KV_HANDLE_MAGIC 0xfa61a402 - -/*! Internal structure of keyvalue datastore handle. - */ -struct kv_handle { - int kh_magic; /* magic */ - char *kh_dbdir; /* Directory of database files */ - yang_spec *kh_yangspec; /* Yang spec if this datastore */ -}; - -/*! Check struct magic number for sanity checks - * return 0 if OK, -1 if fail. - */ -static int -kv_handle_check(xmldb_handle xh) -{ - /* Dont use handle macro to avoid recursion */ - struct kv_handle *kh = (struct kv_handle *)(xh); - - return kh->kh_magic == KV_HANDLE_MAGIC ? 0 : -1; -} - -/*! Database locking for candidate and running non-persistent - * Store an integer for running and candidate containing - * the session-id of the client holding the lock. - */ -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] xh XMLDB handle - * @param[in] db Symbolic database name, eg "candidate", "running" - * @param[out] filename Filename. Unallocate after use with free() - * @retval 0 OK - * @retval -1 Error - * @note Could need a way to extend which databases exists, eg to register new. - * The currently allowed databases are: - * candidate, tmp, running, result - * The filename reside in CLICON_XMLDB_DIR option - */ -static int -kv_db2file(struct kv_handle *kh, - const char *db, - char **filename) -{ - int retval = -1; - cbuf *cb; - char *dir; - - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - if ((dir = kh->kh_dbdir) == NULL){ - 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"); - goto done; - } - retval = 0; - done: - if (cb) - cbuf_free(cb); - return retval; -} - -/*! Help function to append key values from an xml list to a cbuf - * Example, a yang node x with keys a and b results in "x/a/b" - */ -static int -append_listkeys(cbuf *ckey, - cxobj *xt, - yang_stmt *ys) -{ - int retval = -1; - yang_stmt *ykey; - cxobj *xkey; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *keyname; - char *bodyenc; - int i=0; - - cvk = ys->ys_cvec; /* Use Y_LIST cache, see ys_populate_list() */ - cvi = NULL; - /* Iterate over individual keys */ - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - keyname = cv_string_get(cvi); - if ((xkey = xml_find(xt, keyname)) == NULL){ - clicon_err(OE_XML, errno, "XML list node \"%s\" does not have key \"%s\" child", - xml_name(xt), keyname); - goto done; - } - if (uri_percent_encode(&bodyenc, "%s", xml_body(xkey)) < 0) - goto done; - if (i++) - cprintf(ckey, ","); - else - cprintf(ckey, "="); - cprintf(ckey, "%s", bodyenc); - free(bodyenc); - bodyenc = NULL; - } - retval = 0; - done: - return retval; -} - -/*! Help function to create xml key values - * @param[in,out] x Parent - * @param[in] ykey - * @param[in] arg - * @param[in] keyname yang key name - */ -static int -create_keyvalues(cxobj *x, - yang_stmt *ykey, - char *arg, - char *keyname) -{ - int retval = -1; - cxobj *xn; - cxobj *xb; - - /* Check if key node exists */ - if ((xn = xml_new_spec(keyname, x, ykey)) == NULL) - goto done; - if ((xb = xml_new("body", xn)) == NULL) - goto done; - xml_type_set(xb, CX_BODY); - xml_value_set(xb, arg); - retval = 0; - done: - return retval; -} - - -/*! - * @param[in] xk xmlkey - * @param[out] xt XML tree as result - * XXX cannot handle top-level list - */ -static int -get(char *dbname, - yang_spec *ys, - char *xk, - char *val, - cxobj *xt) -{ - int retval = -1; - char **vec = NULL; - int nvec; - char **valvec = NULL; - int nvalvec; - int i; - int j; - char *name; - char *restval; - yang_stmt *y; - cxobj *x; - cxobj *xc; - cxobj *xb; - yang_stmt *ykey; - cg_var *cvi; - cvec *cvk = NULL; /* vector of index keys */ - char *keyname; - char *arg; - char *argdec; - cbuf *cb; - - // clicon_debug(1, "%s xkey:%s val:%s", __FUNCTION__, xk, val); - x = xt; - if (xk == NULL || *xk!='/'){ - clicon_err(OE_DB, 0, "Invalid key: %s", xk); - goto done; - } - if ((vec = clicon_strsep(xk, "/", &nvec)) == NULL) - goto done; - /* Element 0 is NULL '/', - Element 1 is top symbol and needs to find subs in all modules: - spec->module->syntaxnode - */ - if (nvec < 2){ - clicon_err(OE_XML, 0, "Malformed key: %s", xk); - goto done; - } - i = 1; - while (i name:x restval=1,2 */ - if ((restval = index(name, '=')) != NULL){ - *restval = '\0'; - restval++; - } - if (i == 1){ /* spec->module->node */ - if ((y = yang_find_topnode(ys, name, YC_DATANODE)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - } - else - if ((y = yang_find_datanode((yang_node*)y, name)) == NULL){ - clicon_err(OE_UNIX, errno, "No yang node found: %s", name); - goto done; - } - switch (y->ys_keyword){ - case Y_LEAF_LIST: - /* - * If xml element is a leaf-list, then the next element is expected to - * be a value - */ - if (uri_percent_decode(&argdec, restval) < 0) - goto done; - if ((xc = xml_find(x, name))==NULL || - (xb = xml_find(xc, argdec))==NULL){ - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - /* Assume body is created at end of function */ - } - free(argdec); - argdec = NULL; - break; - case Y_LIST: - /* - * If xml element is a list, then the next element(s) is expected to be - * a key value. Check if this key value is already in the xml tree, - * otherwise create it. - */ - if ((ykey = yang_find((yang_node*)y, Y_KEY, NULL)) == NULL){ - clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", - __FUNCTION__, y->ys_argument); - goto done; - } - /* The value is a list of keys: [ ]* */ - if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) - goto done; - if ((cb = cbuf_new()) == NULL){ - clicon_err(OE_XML, errno, "cbuf_new"); - goto done; - } - cvi = NULL; - /* Iterate over individual yang keys */ - cprintf(cb, "%s", name); - if (valvec) - free(valvec); - if ((valvec = clicon_strsep(restval, ",", &nvalvec)) == NULL) - goto done; - if (cvec_len(cvk)!=nvalvec){ - retval = 0; - goto done; - } - j = 0; - while ((cvi = cvec_each(cvk, cvi)) != NULL){ - if (j>=nvalvec) - break; - arg = valvec[j++]; - if (uri_percent_decode(arg, &argdec) < 0) - goto done; - cprintf(cb, "[%s='%s']", cv_string_get(cvi), argdec); - free(argdec); - argdec=NULL; - } - if ((xc = xpath_first(x, cbuf_get(cb))) == NULL){ - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - cvi = NULL; - // i -= cvec_len(cvk); - /* Iterate over individual yang keys */ - j=0; - while ((cvi = cvec_each(cvk, cvi)) != NULL) { - if (j>=nvalvec) - break; - arg = valvec[j++]; - keyname = cv_string_get(cvi); - if (uri_percent_decode(arg, &argdec) < 0) - goto done; - if (create_keyvalues(xc, - ykey, - argdec, - keyname) < 0) - goto done; - free(argdec); - argdec = NULL; - } /* while */ - } - if (cb){ - cbuf_free(cb); - cb = NULL; - } - if (cvk){ - cvec_free(cvk); - cvk = NULL; - } - break; - case Y_LEAF: - case Y_CONTAINER: - default: - if ((xc = xml_find(x, name))==NULL) - if ((xc = xml_new_spec(name, x, y)) == NULL) - goto done; - break; - } /* switch */ - x = xc; - i++; - } - if (val && xml_body(x)==NULL){ - if ((x = xml_new("body", x)) == NULL) - goto done; - xml_type_set(x, CX_BODY); - xml_value_set(x, val); - } - if(debug>1){ - fprintf(stderr, "%s %s\n", __FUNCTION__, xk); - clicon_xml2file(stderr, xt, 0, 1); - } - retval = 0; - done: - if (vec) - free(vec); - if (valvec) - free(valvec); - if (cvk) - cvec_free(cvk); - return retval; -} - -/*! Connect to a datastore plugin - * @retval handle Use this handle for other API calls - * @retval NULL Error - * @note You can do several connects, and have multiple connections to the same - * datastore - */ -xmldb_handle -kv_connect(void) -{ - struct kv_handle *kh; - xmldb_handle xh = NULL; - int size; - - size = sizeof(struct kv_handle); - if ((kh = malloc(size)) == NULL){ - clicon_err(OE_UNIX, errno, "malloc"); - goto done; - } - memset(kh, 0, size); - kh->kh_magic = KV_HANDLE_MAGIC; - xh = (xmldb_handle)kh; - done: - return xh; -} - -/*! Disconnect from a datastore plugin and deallocate handle - * @param[in] handle Disconect and deallocate from this handle - * @retval 0 OK - */ -int -kv_disconnect(xmldb_handle xh) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - - if (kh){ - if (kh->kh_dbdir) - free(kh->kh_dbdir); - free(kh); - } - retval = 0; - // done: - return retval; -} - -/*! Get value of generic plugin option. Type of value is givenby context - * @param[in] xh XMLDB handle - * @param[in] optname Option name - * @param[out] value Pointer to Value of option - * @retval 0 OK - * @retval -1 Error - */ -int -kv_getopt(xmldb_handle xh, - char *optname, - void **value) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - - if (strcmp(optname, "yangspec") == 0) - *value = kh->kh_yangspec; - else if (strcmp(optname, "dbdir") == 0) - *value = kh->kh_dbdir; - else{ - clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Set value of generic plugin option. Type of value is givenby context - * @param[in] xh XMLDB handle - * @param[in] optname Option name - * @param[in] value Value of option - * @retval 0 OK - * @retval -1 Error - */ -int -kv_setopt(xmldb_handle xh, - char *optname, - void *value) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - - if (strcmp(optname, "yangspec") == 0) - kh->kh_yangspec = (yang_spec*)value; - else if (strcmp(optname, "dbdir") == 0){ - if (value && (kh->kh_dbdir = strdup((char*)value)) == NULL){ - clicon_err(OE_UNIX, 0, "strdup"); - goto done; - } - } - else{ - clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! 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. - * This is a clixon datastore plugin of the the xmldb api - * @see xmldb_get - */ -int -kv_get(xmldb_handle xh, - const char *db, - char *xpath, - int config, - cxobj **xtop) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - yang_spec *yspec; - char *dbfile = NULL; - cxobj **xvec = NULL; - size_t xlen; - int i; - int npairs; - struct db_pair *pairs; - cxobj *xt = NULL; - - clicon_debug(2, "%s", __FUNCTION__); - if (kv_db2file(kh, db, &dbfile) < 0) - goto done; - if (dbfile==NULL){ - clicon_err(OE_XML, 0, "dbfile NULL"); - goto done; - } - if ((yspec = kh->kh_yangspec) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - /* Read in complete database (this can be optimized) */ - if ((npairs = db_regexp(dbfile, "", __FUNCTION__, &pairs, 0)) < 0) - goto done; - if ((xt = xml_new_spec("config", NULL, yspec)) == NULL) - goto done; - /* Translate to complete xml tree */ - for (i = 0; i < npairs; i++) { - if (get(dbfile, - yspec, - pairs[i].dp_key, /* xml key */ - pairs[i].dp_val, /* may be NULL */ - xt) < 0) - goto done; - } - if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0) - goto done; - /* If vectors are specified then filter out everything else, - * otherwise return complete tree. - */ - if (xvec != NULL){ - for (i=0; i1) - clicon_xml2file(stderr, xt, 0, 1); - *xtop = xt; - retval = 0; - done: - if (dbfile) - free(dbfile); - if (xvec) - free(xvec); - unchunk_group(__FUNCTION__); - return retval; - -} - -/*! Add data to database internal recursive function - * @param[in] dbfile Name of database to search in (filename incl dir path) - * @param[in] xt xml-node. - * @param[in] ys Yang statement corresponding to xml-node - * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[in] xkey0 aggregated xmlkey - * @retval 0 OK - * @retval -1 Error - * @note XXX op only supports merge - */ -static int -put(char *dbfile, - cxobj *xt, - yang_stmt *ys, - enum operation_type op, - const char *xk0) -{ - int retval = -1; - cxobj *x = NULL; - char *xk; - cbuf *cbxk = NULL; - char *body; - yang_stmt *y; - int exists; - char *bodyenc=NULL; - char *opstr; - - clicon_debug(1, "%s xk0:%s ys:%s", __FUNCTION__, xk0, ys->ys_argument); - if (debug){ - xml_print(stderr, xt); - // yang_print(stderr, (yang_node*)ys); - } - if ((opstr = xml_find_value(xt, "operation")) != NULL) - if (xml_operation(opstr, &op) < 0) - goto done; - body = xml_body(xt); - if ((cbxk = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbxk, "%s/%s", xk0, xml_name(xt)); - switch (ys->ys_keyword){ - case Y_LIST: /* Note: can have many keys */ - if (append_listkeys(cbxk, xt, ys) < 0) - goto done; - break; - case Y_LEAF_LIST: - if (uri_percent_encode(&bodyenc, "%s", body) < 0) - goto done; - cprintf(cbxk, "=%s", bodyenc); - break; - default: - break; - } - xk = cbuf_get(cbxk); - // fprintf(stderr, "%s %s\n", key, body?body:""); - /* Write to database, key and a vector of variables */ - switch (op){ - case OP_CREATE: - if ((exists = db_exists(dbfile, xk)) < 0) - goto done; - if (exists == 1){ - clicon_err(OE_DB, 0, "OP_CREATE: %s already exists in database", xk); - goto done; - } - case OP_MERGE: - case OP_REPLACE: - if (db_set(dbfile, xk, body?body:NULL, body?strlen(body)+1:0) < 0) - goto done; - break; - case OP_DELETE: - if ((exists = db_exists(dbfile, xk)) < 0) - goto done; - if (exists == 0){ - clicon_err(OE_DB, 0, "OP_DELETE: %s does not exists in database", xk); - goto done; - } - case OP_REMOVE: - switch (ys->ys_keyword){ - case Y_LIST: - case Y_CONTAINER:{ - struct db_pair *pairs; - int npairs; - cbuf *cbrx; - int i; - - if ((cbrx = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - cprintf(cbrx, "^%s.*$", xk); - if ((npairs = db_regexp(dbfile, cbuf_get(cbrx), __FUNCTION__, - &pairs, 0)) < 0) - goto done; - /* Translate to complete xml tree */ - for (i = 0; i < npairs; i++) - if (db_del(dbfile, pairs[i].dp_key) < 0) - goto done; - if (cbrx) - cbuf_free(cbrx); - /* Skip recursion, we have deleted whole subtree */ - retval = 0; - goto done; - break; - } - default: - if (db_del(dbfile, xk) < 0) - goto done; - break; - } - - break; - case OP_NONE: - break; - } - /* For every node, create a key with values */ - while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL){ - if ((y = yang_find_datanode((yang_node*)ys, xml_name(x))) == NULL){ - clicon_err(OE_UNIX, 0, "No yang node found: %s", xml_name(x)); - goto done; - } - if (put(dbfile, x, y, op, xk) < 0) - goto done; - } - retval = 0; - done: - if (cbxk) - cbuf_free(cbxk); - if (bodyenc) - free(bodyenc); - unchunk_group(__FUNCTION__); - return retval; -} - -/*! Modify database provided an xml tree and an operation - * This is a clixon datastore plugin of the the xmldb api - * @see xmldb_put - */ -int -kv_put(xmldb_handle xh, - const char *db, - enum operation_type op, - cxobj *xt) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - cxobj *x = NULL; - yang_stmt *ys; - yang_spec *yspec; - char *dbfilename = NULL; - - if ((yspec = kh->kh_yangspec) == NULL){ - clicon_err(OE_YANG, ENOENT, "No yang spec"); - goto done; - } - if (kv_db2file(kh, db, &dbfilename) < 0) - goto done; - if (op == OP_REPLACE){ - if (db_delete(dbfilename) < 0) - goto done; - if (db_init(dbfilename) < 0) - goto done; - } - // 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), YC_DATANODE)) == 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; - } - retval = 0; - done: - if (dbfilename) - free(dbfilename); - return retval; -} - -/*! Copy database from db1 to db2 - * @param[in] xh XMLDB handle - * @param[in] from Source database copy - * @param[in] to Destination database - * @retval -1 Error - * @retval 0 OK - */ -int -kv_copy(xmldb_handle xh, - const char *from, - const char *to) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *fromfile = NULL; - char *tofile = NULL; - - /* XXX lock */ - if (kv_db2file(kh, from, &fromfile) < 0) - goto done; - if (kv_db2file(kh, to, &tofile) < 0) - goto done; - if (clicon_file_copy(fromfile, tofile) < 0) - goto done; - retval = 0; - done: - if (fromfile) - free(fromfile); - if (tofile) - free(tofile); - return retval; -} - -/*! Lock database - * @param[in] xh XMLDB handle - * @param[in] db Database - * @param[in] pid Process id - * @retval -1 Error - * @retval 0 OK - */ -int -kv_lock(xmldb_handle xh, - const char *db, - int pid) -{ - int retval = -1; - // struct kv_handle *kh = handle(xh); - if (strcmp("running", db) == 0) - _running_locked = pid; - else if (strcmp("candidate", db) == 0) - _candidate_locked = pid; - else if (strcmp("startup", db) == 0) - _startup_locked = pid; - else{ - clicon_err(OE_DB, 0, "No such database: %s", db); - goto done; - } - clicon_debug(1, "%s: locked by %u", db, pid); - retval = 0; - done: - return retval; -} - -/*! Unlock database - * @param[in] xh XMLDB handle - * @param[in] db Database - * @param[in] pid Process id - * @retval -1 Error - * @retval 0 OK - * Assume all sanity checks have been made - */ -int -kv_unlock(xmldb_handle xh, - const char *db) -{ - int retval = -1; - // struct kv_handle *kh = handle(xh); - if (strcmp("running", db) == 0) - _running_locked = 0; - else if (strcmp("candidate", db) == 0) - _candidate_locked = 0; - else if (strcmp("startup", db) == 0) - _startup_locked = 0; - else{ - clicon_err(OE_DB, 0, "No such database: %s", db); - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Unlock all databases locked by pid (eg process dies) - * @param[in] xh XMLDB handle - * @param[in] pid Process / Session id - * @retval -1 Error - * @retval 0 Ok - */ -int -kv_unlock_all(xmldb_handle xh, - int pid) -{ - // struct kv_handle *kh = handle(xh); - - if (_running_locked == pid) - _running_locked = 0; - if (_candidate_locked == pid) - _candidate_locked = 0; - if (_startup_locked == pid) - _startup_locked = 0; - return 0; -} - -/*! Check if database is locked - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 Not locked - * @retval >0 Id of locker - */ -int -kv_islocked(xmldb_handle xh, - const char *db) -{ - int retval = -1; - // struct kv_handle *kh = handle(xh); - - if (strcmp("running", db) == 0) - retval = _running_locked; - else if (strcmp("candidate", db) == 0) - retval = _candidate_locked; - else if (strcmp("startup", db) == 0) - retval = _startup_locked; - else - clicon_err(OE_DB, 0, "No such database: %s", db); - return retval; -} - -/*! Check if db exists - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 No it does not exist - * @retval 1 Yes it exists - */ -int -kv_exists(xmldb_handle xh, - const char *db) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *filename = NULL; - struct stat sb; - - if (kv_db2file(kh, db, &filename) < 0) - goto done; - if (lstat(filename, &sb) < 0) - retval = 0; - else - retval = 1; - done: - if (filename) - free(filename); - return retval; -} - -/*! Delete database. Remove file - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval -1 Error - * @retval 0 OK - */ -int -kv_delete(xmldb_handle xh, - const char *db) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *filename = NULL; - - if (kv_db2file(kh, db, &filename) < 0) - goto done; - if (db_delete(filename) < 0) - goto done; - retval = 0; - done: - if (filename) - free(filename); - return retval; -} - -/*! Create / Initialize database - * @param[in] xh XMLDB handle - * @param[in] db Database - * @retval 0 OK - * @retval -1 Error - */ -int -kv_create(xmldb_handle xh, - const char *db) -{ - int retval = -1; - struct kv_handle *kh = handle(xh); - char *filename = NULL; - - if (kv_db2file(kh, db, &filename) < 0) - goto done; - if (db_init(filename) < 0) - goto done; - retval = 0; - done: - if (filename) - free(filename); - return retval; -} - -/*! plugin init function */ -int -kv_plugin_exit(void) -{ - return 0; -} - -static const struct xmldb_api api; - -/*! plugin init function */ -void * -clixon_xmldb_plugin_init(int version) -{ - if (version != XMLDB_API_VERSION){ - clicon_err(OE_DB, 0, "Invalid version %d expected %d", - version, XMLDB_API_VERSION); - goto done; - } - return (void*)&api; - done: - return NULL; -} - -static const struct xmldb_api api = { - 1, - XMLDB_API_MAGIC, - clixon_xmldb_plugin_init, - kv_plugin_exit, - kv_connect, - kv_disconnect, - kv_getopt, - kv_setopt, - kv_get, - kv_put, - kv_copy, - kv_lock, - kv_unlock, - kv_unlock_all, - kv_islocked, - kv_exists, - kv_delete, - kv_create, -}; - - -#if 0 /* Test program */ -/* - * Turn this on to get an xpath test program - * Usage: clicon_xpath [] - * read xml from input - * Example compile: - gcc -g -o keyvalue -I. -I../../lib ./clixon_keyvalue.c clixon_chunk.c clixon_qdb.c -lclixon -lcligen -lqdbm -*/ - -/*! Raw dump of database, just keys and values, no xml interpretation - * @param[in] f File - * @param[in] dbfile File-name of database. This is a local file - * @param[in] rxkey Key regexp, eg "^.*$" - * @note This function can only be called locally. - */ -int -main(int argc, - char **argv) -{ - int retval = -1; - int npairs; - struct db_pair *pairs; - char *rxkey = NULL; - char *dbfilename; - - if (argc != 2 && argc != 3){ - fprintf(stderr, "usage: %s [rxkey]\n", argv[0]); - goto done; - } - dbfilename = argv[1]; - if (argc == 3) - rxkey = argv[2]; - else - rxkey = "^.*$"; /* Default is match all */ - - /* Get all keys/values for vector */ - if ((npairs = db_regexp(dbfilename, rxkey, __FUNCTION__, &pairs, 0)) < 0) - goto done; - - for (npairs--; npairs >= 0; npairs--) - fprintf(stdout, "%s %s\n", pairs[npairs].dp_key, - pairs[npairs].dp_val?pairs[npairs].dp_val:""); - retval = 0; - done: - unchunk_group(__FUNCTION__); - return retval; -} - -#endif /* Test program */ diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h deleted file mode 100644 index b11e15d5..00000000 --- a/datastore/keyvalue/clixon_keyvalue.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - Key-value store - */ -#ifndef _CLIXON_KEYVALUE_H -#define _CLIXON_KEYVALUE_H - -/* - * Prototypes - */ -int kv_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop); -int kv_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt); -int kv_dump(FILE *f, char *dbfilename, char *rxkey); -int kv_copy(xmldb_handle h, const char *from, const char *to); -int kv_lock(xmldb_handle h, const char *db, int pid); -int kv_unlock(xmldb_handle h, const char *db); -int kv_unlock_all(xmldb_handle h, int pid); -int kv_islocked(xmldb_handle h, const char *db); -int kv_exists(xmldb_handle h, const char *db); -int kv_delete(xmldb_handle h, const char *db); -int kv_init(xmldb_handle h, const char *db); - -#endif /* _CLIXON_KEYVALUE_H */ diff --git a/datastore/keyvalue/clixon_qdb.c b/datastore/keyvalue/clixon_qdb.c deleted file mode 100644 index b80daf71..00000000 --- a/datastore/keyvalue/clixon_qdb.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - * @note Some unclarities with locking. man dpopen defines the following flags - * with dpopen: - * `DP_ONOLCK', which means it opens a database file without - * file locking, - * `DP_OLCKNB', which means locking is performed without blocking. - * - * While connecting as a writer, an exclusive lock is invoked to - * the database file. While connecting as a reader, a shared lock is - * invoked to the database file. The thread blocks until the lock is - * achieved. If `DP_ONOLCK' is used, the application is responsible - * for exclusion control. - * The code below uses for - * write, delete: DP_OLCKNB - * read: DP_OLCKNB - * This means that a write fails if one or many reads are occurring, and - * a read or write fails if a write is occurring, and - * QDBM allows a single write _or_ multiple readers, but - * not both. This is obviously extremely limiting. - * NOTE, the locking in netconf and xmldb is a write lock. - */ - -#ifdef HAVE_CONFIG_H -#include "clixon_config.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_DEPOT_H -#include /* qdb api */ -#else /* HAVE_QDBM_DEPOT_H */ -#include /* qdb api */ -#endif - -#include - -/* clicon */ -#include - -#include "clixon_chunk.h" -#include "clixon_qdb.h" - -/*! Initialize database - * @param[in] file database file - * @param[in] omode see man dpopen - */ -static int -db_init_mode(char *file, - int omode) -{ - DEPOT *dp; - - /* Open database for writing */ - if ((dp = dpopen(file, omode | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "dpopen(%s): %s", - file, - dperrmsg(dpecode)); - return -1; - } - clicon_debug(1, "db_init(%s)", file); - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_set: dpclose: %s", - dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Open database for reading and writing - * @param[in] file database file - */ -int -db_init(char *file) -{ - return db_init_mode(file, DP_OWRITER | DP_OCREAT ); /* DP_OTRUNC? */ -} - -/*! Remove database by removing file, if it exists * - * @param[in] file database file - */ -int -db_delete(char *file) -{ - struct stat sb; - - if (stat(file, &sb) < 0){ - return 0; - } - if (unlink(file) < 0){ - clicon_err(OE_DB, errno, "unlink %s", file); - return -1; - } - return 0; -} - -/*! Write data to database - * @param[in] file database file - * @param[in] key database key - * @param[out] data Buffer containing content - * @param[out] datalen Length of buffer - * @retval 0 if OK: value returned. If not found, zero string returned - * @retval -1 on error - */ -int -db_set(char *file, - char *key, - void *data, - size_t datalen) -{ - DEPOT *dp; - - /* Open database for writing */ - if ((dp = dpopen(file, DP_OWRITER|DP_OLCKNB , 0)) == NULL){ - clicon_err(OE_DB, errno, "db_set: dpopen(%s): %s", - file, - dperrmsg(dpecode)); - return -1; - } - clicon_debug(2, "%s: db_put(%s, len:%d)", - file, key, (int)datalen); - if (dpput(dp, key, -1, data, datalen, DP_DOVER) == 0){ - clicon_err(OE_DB, errno, "%s: db_set: dpput(%s, %d): %s", - file, - key, - datalen, - dperrmsg(dpecode)); - dpclose(dp); - return -1; - } - if (dpclose(dp) == 0){ - clicon_err(OE_DB, 0, "db_set: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Get data from database - * @param[in] file database file - * @param[in] key database key - * @param[out] data Pre-allocated buffer where data corresponding key is placed - * @param[out] datalen Length of pre-allocated buffer - * @retval 0 if OK: value returned. If not found, zero string returned - * @retval -1 on error - * @see db_get_alloc Allocates memory - */ -int -db_get(char *file, - char *key, - void *data, - size_t *datalen) -{ - DEPOT *dp; - int len; - - /* Open database for readinf */ - if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "%s: db_get(%s, %d): dpopen: %s", - file, - key, - datalen, - dperrmsg(dpecode)); - return -1; - } - len = dpgetwb(dp, key, -1, 0, *datalen, data); - if (len < 0){ - if (dpecode == DP_ENOITEM){ - data = NULL; - *datalen = 0; - } - else{ - clicon_err(OE_DB, errno, "db_get: dpgetwb: %s (%d)", - dperrmsg(dpecode), dpecode); - dpclose(dp); - return -1; - } - } - else - *datalen = len; - clicon_debug(2, "db_get(%s, %s)=%s", file, key, (char*)data); - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_get: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Get data from database and allocates memory - * Similar to db_get but returns a malloced pointer to the data instead - * of copying data to pre-allocated buffer. This is necessary if the - * length of the data is not known when calling the function. - * @param[in] file database file - * @param[in] key database key - * @param[out] data Allocated buffer where data corresponding key is placed - * @param[out] datalen Length of pre-allocated buffer - * @retval 0 if OK: value returned. If not found, zero string returned - * @retval -1 on error - * @note: *data needs to be freed after use. - * @code - * char *lvec = NULL; - * size_t len = 0; - * if (db_get-alloc(dbname, "a.0", &val, &vlen) == NULL) - * return -1; - * ..do stuff.. - * if (val) free(val); - * @endcode - * @see db_get Pre-allocates memory - */ -int -db_get_alloc(char *file, - char *key, - void **data, - size_t *datalen) -{ - DEPOT *dp; - int len; - - /* Open database for writing */ - if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "%s: dpopen(%s): %s", - __FUNCTION__, - file, - dperrmsg(dpecode)); - return -1; - } - if ((*data = dpget(dp, key, -1, 0, -1, &len)) == NULL){ - if (dpecode == DP_ENOITEM){ - *datalen = 0; - *data = NULL; - len = 0; - } - else{ - /* No entry vs error? */ - clicon_err(OE_DB, errno, "db_get_alloc: dpgetwb: %s (%d)", - dperrmsg(dpecode), dpecode); - dpclose(dp); - return -1; - } - } - *datalen = len; - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_get_alloc: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return 0; -} - -/*! Delete database entry - * @param[in] file database file - * @param[in] key database key - * @retval -1 on failure, - * @retval 0 if key did not exist - * @retval 1 if successful. - */ -int -db_del(char *file, char *key) -{ - int retval = 0; - DEPOT *dp; - - /* Open database for writing */ - if ((dp = dpopen(file, DP_OWRITER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "db_del: dpopen(%s): %s", - file, - dperrmsg(dpecode)); - return -1; - } - if (dpout(dp, key, -1)) { - retval = 1; - } - if (dpclose(dp) == 0){ - clicon_err(OE_DB, errno, "db_del: dpclose: %s", dperrmsg(dpecode)); - return -1; - } - return retval; -} - -/*! Check if entry in database exists - * @param[in] file database file - * @param[in] key database key - * @retval 1 if key exists in database - * @retval 0 key does not exist in database - * @retval -1 error - */ -int -db_exists(char *file, - char *key) -{ - DEPOT *dp; - int len; - - /* Open database for reading */ - if ((dp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "%s: dpopen: %s", - __FUNCTION__, dperrmsg(dpecode)); - return -1; - } - - len = dpvsiz(dp, key, -1); - if (len < 0 && dpecode != DP_ENOITEM) - clicon_err(OE_DB, errno, "^s: dpvsiz: %s (%d)", - __FUNCTION__, dperrmsg(dpecode), dpecode); - - if (dpclose(dp) == 0) { - clicon_err(OE_DB, errno, "%s: dpclose: %s", dperrmsg(dpecode),__FUNCTION__); - return -1; - } - - return (len < 0) ? 0 : 1; -} - -/*! Return all entries in database that match a regular expression. - * @param[in] file database file - * @param[in] regexp regular expression for database keys - * @param[in] label for memory/chunk allocation - * @param[out] pairs Vector of database keys and values - * @param[in] noval If set don't retreive values, just keys - * @retval -1 on error - * @retval n Number of pairs - * @code - * struct db_pair *pairs; - * int npairs; - * if ((npairs = db_regexp(dbname, "^/test/kalle$", __FUNCTION__, - * &pairs, 0)) < 0) - * err; - * - * @endcode - */ -int -db_regexp(char *file, - char *regexp, - const char *label, - struct db_pair **pairs, - int noval) -{ - int npairs; - int status; - int retval = -1; - int vlen = 0; - char *key = NULL; - void *val = NULL; - char errbuf[512]; - struct db_pair *pair; - struct db_pair *newpairs; - regex_t iterre; - DEPOT *iterdp = NULL; - regmatch_t pmatch[1]; - size_t nmatch = 1; - - npairs = 0; - *pairs = NULL; - - if (regexp) { - if ((status = regcomp(&iterre, regexp, REG_EXTENDED)) != 0) { - regerror(status, &iterre, errbuf, sizeof(errbuf)); - clicon_err(OE_DB, errno, "%s: regcomp: %s", __FUNCTION__, errbuf); - return -1; - } - } - - /* Open database for reading */ - if ((iterdp = dpopen(file, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, 0, "%s: dpopen(%s): %s", - __FUNCTION__, file, dperrmsg(dpecode)); - goto quit; - } - - /* Initiate iterator */ - if(dpiterinit(iterdp) == 0) { - clicon_err(OE_DB, errno, "%s: dpiterinit: %s", __FUNCTION__, dperrmsg(dpecode)); - goto quit; - } - - /* Iterate through DB */ - while((key = dpiternext(iterdp, NULL)) != NULL) { - - if (regexp && regexec(&iterre, key, nmatch, pmatch, 0) != 0) { - free(key); - continue; - } - - /* Retrieve value if required */ - if ( ! noval) { - if((val = dpget(iterdp, key, -1, 0, -1, &vlen)) == NULL) { - clicon_log(LOG_WARNING, "%s: dpget: %s", __FUNCTION__, dperrmsg(dpecode)); - goto quit; - } - } - - /* Resize and populate resulting array */ - newpairs = rechunk(*pairs, (npairs+1) * sizeof(struct db_pair), label); - if (newpairs == NULL) { - clicon_err(OE_DB, errno, "%s: rechunk", __FUNCTION__); - goto quit; - } - pair = &newpairs[npairs]; - memset(pair, 0, sizeof(*pair)); - - pair->dp_key = chunk_sprintf(label, "%s", key); - if (regexp) - pair->dp_matched = chunk_sprintf(label, "%.*s", - pmatch[0].rm_eo - pmatch[0].rm_so, - key + pmatch[0].rm_so); - else - pair->dp_matched = chunk_sprintf(label, "%s", key); - if (pair->dp_key == NULL || pair->dp_matched == NULL) { - clicon_err(OE_DB, errno, "%s: chunk_sprintf"); - goto quit; - } - if ( ! noval) { - if (vlen){ - pair->dp_val = chunkdup(val, vlen, label); - if (pair->dp_val == NULL) { - clicon_err(OE_DB, errno, "%s: chunkdup", __FUNCTION__); - goto quit; - } - } - pair->dp_vlen = vlen; - free(val); - val = NULL; - } - - (*pairs) = newpairs; - npairs++; - free(key); - } - - retval = npairs; - -quit: - if (key) - free(key); - if (val) - free(val); - if (regexp) - regfree(&iterre); - if (iterdp) - dpclose(iterdp); - if (retval < 0) - unchunk_group(label); - - return retval; -} - -/*! Sanitize regexp string. Escape '\' etc. - */ -char * -db_sanitize(char *rx, const char *label) -{ - char *new; - char *k, *p, *s; - - k = chunk_sprintf(__FUNCTION__, "%s", ""); - p = rx; - while((s = strstr(p, "\\"))) { - if ((k = chunk_sprintf(__FUNCTION__, "%s%.*s\\\\", k, s-p, p)) == NULL) - goto quit; - p = s+1; - } - if ((k = chunk_strncat(k, p, strlen(p), __FUNCTION__)) == NULL) - goto quit; - - new = (char *)chunkdup(k, strlen(k)+1, label); - unchunk_group(__FUNCTION__); - return new; - - quit: - unchunk_group(__FUNCTION__); - return NULL; -} - -#if 0 /* Test program */ -/* - * Turn this on to get an xpath test program - * Usage: clicon_xpath [] - * read xml from input - * Example compile: - gcc -g -o qdb -I. -I../clixon ./clixon_qdb.c -lclixon -lcligen -lqdbm -*/ - -static int -usage(char *argv0) -{ - fprintf(stderr, "usage:\n"); - fprintf(stderr, "\t%s init \n", argv0); - fprintf(stderr, "\t%s read \n", argv0); - fprintf(stderr, "\t%s write \n", argv0); - fprintf(stderr, "\t%s openread \n", argv0); - fprintf(stderr, "\t%s openwrite \n", argv0); - exit(0); -} - -int -main(int argc, char **argv) -{ - char *verb; - char *filename; - char *key; - char *val; - size_t len; - DEPOT *dp; - - if (argc < 3) - usage(argv[0]); - clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); - verb = argv[1]; - filename = argv[2]; - if (strcmp(verb, "init")==0){ - db_init(filename); - } - else if (strcmp(verb, "read")==0){ - if (argc < 4) - usage(argv[0]); - key = argv[3]; - db_get_alloc(filename, key, (void**)&val, &len); - fprintf(stdout, "%s\n", val); - } - else if (strcmp(verb, "write")==0){ - if (argc < 5) - usage(argv[0]); - key = argv[3]; - val = argv[4]; - db_set(filename, key, val, strlen(val)+1); - } - else if (strcmp(verb, "openread")==0){ - if ((dp = dpopen(filename, DP_OREADER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "dbopen: %s", - dperrmsg(dpecode)); - return -1; - } - sleep(1000000); - } - else if (strcmp(verb, "openwrite")==0){ - if ((dp = dpopen(filename, DP_OWRITER | DP_OLCKNB, 0)) == NULL){ - clicon_err(OE_DB, errno, "dbopen: %s", - dperrmsg(dpecode)); - return -1; - } - sleep(1000000); - } - return 0; -} - -#endif /* Test program */ - - diff --git a/datastore/keyvalue/clixon_qdb.h b/datastore/keyvalue/clixon_qdb.h deleted file mode 100644 index d2d38785..00000000 --- a/datastore/keyvalue/clixon_qdb.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - * - ***** BEGIN LICENSE BLOCK ***** - - Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren - - 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 ***** - - */ - -#ifndef _CLIXON_QDB_H_ -#define _CLIXON_QDB_H_ - - -/* - * Low level API - */ - -struct db_pair { - char *dp_key; /* database key */ - char *dp_matched; /* Matched component of key */ - char *dp_val; /* pointer to vector of lvalues */ - int dp_vlen; /* length of vector of lvalues */ -}; - -/* - * Prototypes - */ -int db_init(char *file); - -int db_delete(char *file); - -int db_set(char *file, char *key, void *data, size_t datalen); - -int db_get(char *file, char *key, void *data, size_t *datalen); - -int db_get_alloc(char *file, char *key, void **data, size_t *datalen); - -int db_del(char *file, char *key); - -int db_exists(char *file, char *key); - -int db_regexp(char *file, char *regexp, const char *label, - struct db_pair **pairs, int noval); - -char *db_sanitize(char *rx, const char *label); - -#endif /* _CLIXON_QDB_H_ */ diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 5d1edaf2..8727daa0 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -748,7 +748,8 @@ text_modify(cxobj *x0, * @see text_modify */ static int -text_modify_top(cxobj *x0, +text_modify_top(struct text_handle *th, + cxobj *x0, cxobj *x1, yang_spec *yspec, enum operation_type op, @@ -759,6 +760,7 @@ text_modify_top(cxobj *x0, cxobj *x0c; /* base child */ cxobj *x1c; /* mod child */ yang_stmt *yc; /* yang child */ + yang_stmt *ymod;/* yang module */ char *opstr; /* Assure top-levels are 'config' */ @@ -805,10 +807,23 @@ text_modify_top(cxobj *x0, while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { x1cname = xml_name(x1c); /* Get yang spec of the child */ - if ((yc = yang_find_topnode(yspec, x1cname, YC_DATANODE)) == NULL){ - clicon_err(OE_YANG, ENOENT, "XML node %s/%s has no corresponding yang specification (Invalid XML or wrong Yang spec?", - xml_name(x1), x1cname); + yc = NULL; + if (ys_module_by_xml(yspec, x1c, &ymod) <0) goto done; + if (ymod != NULL) + yc = yang_find_datanode((yang_node*)ymod, x1cname); + if (yc == NULL && _CLICON_XML_NS_ITERATE){ + int i; + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((yc = yang_find_datanode((yang_node*)ymod, x1cname)) != NULL) + break; + } + } + if (yc == NULL){ + if (netconf_operation_failed(cbret, "application", "Validation failed")< 0) + goto done; + goto ok; } /* See if there is a corresponding node in the base tree */ if (match_base_child(x0, x1c, &x0c, yc) < 0) @@ -942,11 +957,12 @@ text_put(xmldb_handle xh, xml_name(x0)); goto done; } - - /* Add yang specification backpointer to all XML nodes */ - /* XXX: where is this created? Add yspec */ +#if 0 + /* Add yang specification backpointer to all XML nodes + * This is already done in from_client_edit_config() */ if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; +#endif #if 0 /* debug */ if (xml_child_sort && xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); @@ -955,7 +971,7 @@ text_put(xmldb_handle xh, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if (text_modify_top(x0, x1, yspec, op, cbret) < 0) + if (text_modify_top(th, x0, x1, yspec, op, cbret) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (cbuf_len(cbret)) diff --git a/example/README.md b/example/README.md index 48ed4595..7621d9b5 100644 --- a/example/README.md +++ b/example/README.md @@ -191,10 +191,9 @@ state data. ## Authentication and NACM The example contains some stubs for authorization according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341): -* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: adm1, wilma, and guest, according to the examples in Appendix A in the RFC. +* A basic auth HTTP callback, see: example_restconf_credentials() containing three example users: andy, wilma, and guest, according to the examples in Appendix A in [RFC8341](https://tools.ietf.org/html/rfc8341). * A NACM backend plugin reporting the mandatory NACM state variables. - ## Systemd files Example systemd files for backend and restconf daemons are found under the systemd directory. Install them under /etc/systemd/system for example. diff --git a/example/example.yang b/example/example.yang index 0ee76e30..57f58fcb 100644 --- a/example/example.yang +++ b/example/example.yang @@ -9,6 +9,7 @@ module example { prefix ip; } import ietf-routing { + description "defines fib-route"; prefix rt; } import iana-if-type { @@ -72,4 +73,13 @@ module example { } } } + rpc debug { + description "Set debug level of backend. XXX should be in clixon-config"; + input { + leaf level { + type uint32; + } + } + } + } diff --git a/example/example_cli.c b/example/example_cli.c index 99c38368..987aae30 100644 --- a/example/example_cli.c +++ b/example/example_cli.c @@ -96,7 +96,7 @@ fib_route_rpc(clicon_handle h, /* User supplied variable in CLI command */ instance = cvec_find(cvv, "instance"); /* get a cligen variable from vector */ /* Create XML for fib-route netconf RPC */ - if (xml_parse_va(&xtop, NULL, "%s", + if (xml_parse_va(&xtop, NULL, "%s", clicon_username_get(h), cv_string_get(instance)) < 0) goto done; diff --git a/example/example_restconf.c b/example/example_restconf.c index b7d55505..43771662 100644 --- a/example/example_restconf.c +++ b/example/example_restconf.c @@ -191,7 +191,7 @@ b64_decode(const char *src, * @retval -1 Fatal error * @retval 0 Unauth * @retval 1 Auth - * @note: Three hardwired users: adm1, wilma, guest w password "bar". + * @note: Three hardwired users: andy, wilma, guest w password "bar". * Enabled by passing -- -a to the main function */ int @@ -237,9 +237,9 @@ example_restconf_credentials(clicon_handle h, /* Here get auth sub-tree whjere all the users are */ if ((cb = cbuf_new()) == NULL) goto done; - /* Hardcoded user/passwd */ - if (strcmp(user, "wilma")==0 || strcmp(user, "adm1")==0 || - strcmp(user, "quest")==0){ + /* XXX Three hardcoded user/passwd (from RFC8341 A.1)*/ + if (strcmp(user, "wilma")==0 || strcmp(user, "andy")==0 || + strcmp(user, "guest")==0){ passwd2 = "bar"; } if (strcmp(passwd, passwd2)) @@ -282,7 +282,7 @@ restconf_client_rpc(clicon_handle h, /*! Start example restonf plugin. Set authentication method * Arguments are argc/argv after -- * Currently defined: -a enable http basic authentication - * Note hardwired users adm1, wilma and guest + * @note There are three hardwired users andy, wilma and guest from RFC8341 A.1 */ int example_restconf_start(clicon_handle h, diff --git a/extras/rpm/clixon.spec b/extras/rpm/clixon.spec index c6ee5f70..20e77c04 100644 --- a/extras/rpm/clixon.spec +++ b/extras/rpm/clixon.spec @@ -36,7 +36,7 @@ This package contains header files for CLIXON. %setup %build -%configure --with-cligen=%{cligen_prefix} --without-keyvalue +%configure --with-cligen=%{cligen_prefix} make %install diff --git a/lib/Makefile.in b/lib/Makefile.in index eba8991d..d34e81ac 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -80,5 +80,5 @@ distclean: clean do (cd $$i; $(MAKE) $(MFLAGS) distclean); done; \ (cd clixon; $(MAKE) $(MFLAGS) $@) -tags: +TAGS: find $(srcdir) -name '*.[chyl]' -print | etags - diff --git a/lib/clixon/clixon_nacm.h b/lib/clixon/clixon_nacm.h index 34e9a939..978b5486 100644 --- a/lib/clixon/clixon_nacm.h +++ b/lib/clixon/clixon_nacm.h @@ -36,9 +36,19 @@ #ifndef _CLIXON_NACM_H #define _CLIXON_NACM_H +/* + * Constants + */ +/* RFC8341 defines a "recovery session" as outside the scope. + * Clixon defines this user as having special admin rights to expemt from + * all access control enforcements + */ +#define NACM_RECOVERY_USER "_nacm_recovery" + /* * Prototypes */ -int nacm_access(clicon_handle h, char *mode, char *name, char *username, cbuf *cbret); +int nacm_access(clicon_handle h, char *rpc, char *module, + char *username, cbuf *cbret); #endif /* _CLIXON_NACM_H */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 59e25913..803e5948 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -41,6 +41,10 @@ /* * Constants */ +/* If rpc call does not have a namespace (eg w xmlns) then use the default NETCONF + * namespace (rfc6241 3.1) + */ +#define DEFAULT_XML_RPC_NAMESPACE "urn:ietf:params:xml:ns:netconf:base:1.0" /* * Types @@ -81,10 +85,13 @@ typedef int (xml_applyfn_t)(cxobj *x, void *arg); #define XML_FLAG_NONE 0x10 /* Node is added as NONE */ -/* Sort and binary search of XML children - * Experimental +/* Iterate through modules to find the matching datanode + * or rpc if no xmlns attribute specifies namespace. + * This is loose semantics of finding namespaces. + * And it is wrong, but is the way Clixon originally was written." + * @see CLICON_XML_NS_ITERATE clixon configure option */ -extern int xml_child_sort; +extern int _CLICON_XML_NS_ITERATE; /* * Prototypes @@ -94,6 +101,7 @@ char *xml_name(cxobj *xn); int xml_name_set(cxobj *xn, char *name); char *xml_namespace(cxobj *xn); int xml_namespace_set(cxobj *xn, char *name); +int xml2ns(cxobj *x, char *localname, char **namespace); cxobj *xml_parent(cxobj *xn); int xml_parent_set(cxobj *xn, cxobj *parent); @@ -129,6 +137,8 @@ int xml_rootchild(cxobj *xp, int i, cxobj **xcp); char *xml_body(cxobj *xn); cxobj *xml_body_get(cxobj *xn); +char *xml_find_type_value(cxobj *xn_parent, char *prefix, + char *name, enum cxobj_type type); char *xml_find_value(cxobj *xn_parent, char *name); char *xml_find_body(cxobj *xn, char *name); cxobj *xml_find_body_obj(cxobj *xt, char *name, char *val); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index 6a5a32fe..abd1fe50 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -43,6 +43,7 @@ */ int xml2txt(FILE *f, cxobj *x, int level); int xml2cli(FILE *f, cxobj *x, char *prepend, enum genmodel_type gt); +int xml_yang_validate_rpc(cxobj *xrpc); int xml_yang_validate_add(cxobj *xt, void *arg); int xml_yang_validate_all(cxobj *xt, void *arg); int xml2cvec(cxobj *xt, yang_stmt *ys, cvec **cvv0); @@ -60,6 +61,7 @@ int xml_default(cxobj *x, void *arg); int xml_order(cxobj *x, void *arg); int xml_sanity(cxobj *x, void *arg); int xml_non_config_data(cxobj *xt, void *arg); +int xml_spec_populate_rpc(clicon_handle h, cxobj *x, yang_spec *yspec); int xml_spec_populate(cxobj *x, void *arg); int api_path2xpath_cvv(yang_spec *yspec, cvec *cvv, int offset, cbuf *xpath); int api_path2xpath(yang_spec *yspec, char *api_path, cbuf *xpath); diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index a49d1e94..e9aee2e6 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -49,6 +49,7 @@ /* * Types */ +struct xml; /*! YANG keywords from RFC6020. * See also keywords generated by yacc/bison in clicon_yang_parse.tab.h, but they start with K_ * instead of Y_ @@ -159,7 +160,6 @@ typedef enum yang_class yang_class; */ #define yang_datadefinition(y) (yang_datanode(y) || (y)->ys_keyword == Y_CHOICE || (y)->ys_keyword == Y_CASE || (y)->ys_keyword == Y_AUGMENT || (y)->ys_keyword == Y_USES) - /* Yang schema node . * See RFC 7950 Sec 3: * o schema node: A node in the schema tree. One of action, container, @@ -253,15 +253,18 @@ char *yang_key2str(int keyword); char *yarg_prefix(yang_stmt *ys); char *yarg_id(yang_stmt *ys); int yang_nodeid_split(char *nodeid, char **prefix, char **id); +int ys_module_by_xml(yang_spec *ysp, struct xml *xt, yang_stmt **ymodp); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); +yang_stmt *yang_find_module_by_namespace(yang_spec *yspec, char *namespace); yang_stmt *yang_find(yang_node *yn, int keyword, const char *argument); int yang_match(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_datanode(yang_node *yn, char *argument); yang_stmt *yang_find_schemanode(yang_node *yn, char *argument); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name, yang_class class); char *yang_find_myprefix(yang_stmt *ys); +char *yang_find_mynamespace(yang_stmt *ys); int yang_order(yang_stmt *y); int yang_print(FILE *f, yang_node *yn); int yang_print_cbuf(cbuf *cb, yang_node *yn, int marginal); diff --git a/lib/src/clixon_json.c b/lib/src/clixon_json.c index 794b35f2..00778c30 100644 --- a/lib/src/clixon_json.c +++ b/lib/src/clixon_json.c @@ -90,22 +90,43 @@ enum childtype{ ANY_CHILD, /* eg or */ }; +/*! Number of children EXCEPT attributes + * @param[in] xn xml node + * @retval number of children in XML tree (except children of type CX_ATTR) + * @see xml_child_nr + */ +static int +xml_child_nr_noattr(cxobj *xn) +{ + cxobj *x = NULL; + int nr = 0; + + while ((x = xml_child_each(xn, x, -1)) != NULL) { + if (xml_type(x) != CX_ATTR) + nr++; + } + return nr; +} + /*! x is element and has exactly one child which in turn has none + * remove attributes from x * Clone from clixon_xml_map.c */ static enum childtype childtype(cxobj *x) { cxobj *xc1; /* the only child of x */ + int clen; /* nr of children */ + clen = xml_child_nr_noattr(x); if (xml_type(x) != CX_ELMNT) return -1; /* n/a */ - if (xml_child_nr(x) == 0) + if (clen == 0) return NULL_CHILD; - if (xml_child_nr(x) > 1) + if (clen > 1) return ANY_CHILD; xc1 = xml_child_i(x, 0); /* From here exactly one child */ - if (xml_child_nr(xc1) == 0 && xml_type(xc1)==CX_BODY) + if (xml_child_nr_noattr(xc1) == 0 && xml_type(xc1)==CX_BODY) return BODY_CHILD; else return ANY_CHILD; @@ -267,13 +288,13 @@ json_str_escape(char *str) +----------+--------------+--------------+--------------+ */ static int -xml2json1_cbuf(cbuf *cb, - cxobj *x, +xml2json1_cbuf(cbuf *cb, + cxobj *x, enum array_element_type arraytype, - int level, - int pretty, - int flat, - int bodystr) + int level, + int pretty, + int flat, + int bodystr) { int retval = -1; int i; @@ -281,10 +302,30 @@ xml2json1_cbuf(cbuf *cb, enum childtype childt; enum array_element_type xc_arraytype; yang_stmt *ys; + yang_stmt *ymod; /* yang module */ + yang_spec *yspec = NULL; /* yang spec */ int bodystr0=1; + char *str; + char *prefix=NULL; /* prefix / local namespace name */ + char *namespace=NULL; /* namespace uri */ + char *modname=NULL; /* Module name */ + /* If x is labelled with a default namespace, it should be translated + * to a module name. + * Harder if x has a prefix, then that should also be translated to associated + * module name + */ + prefix = xml_namespace(x); + if (xml2ns(x, prefix, &namespace) < 0) + goto done; + if ((ys = xml_spec(x)) != NULL) /* yang spec associated with x */ + yspec = ys_spec(ys); + /* Find module name associated with namspace URI */ + if (namespace && yspec && + (ymod = yang_find_module_by_namespace(yspec, namespace)) != NULL){ + modname = ymod->ys_argument; + } childt = childtype(x); - ys = xml_spec(x); if (pretty==2) cprintf(cb, "#%s_array, %s_child ", arraytype2str(arraytype), @@ -292,7 +333,6 @@ xml2json1_cbuf(cbuf *cb, switch(arraytype){ case BODY_ARRAY:{ if (bodystr){ - char *str; if ((str = json_str_escape(xml_value(x))) == NULL) goto done; cprintf(cb, "\"%s\"", str); @@ -300,14 +340,13 @@ xml2json1_cbuf(cbuf *cb, } else cprintf(cb, "%s", xml_value(x)); - break; } case NO_ARRAY: if (!flat){ cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); - if (xml_namespace(x)) - cprintf(cb, "%s:", xml_namespace(x)); + if (modname) /* XXX should remove this? */ + cprintf(cb, "%s:", modname); cprintf(cb, "%s\": ", xml_name(x)); } switch (childt){ @@ -326,8 +365,8 @@ xml2json1_cbuf(cbuf *cb, case FIRST_ARRAY: case SINGLE_ARRAY: cprintf(cb, "%*s\"", pretty?(level*JSON_INDENT):0, ""); - if (xml_namespace(x)) - cprintf(cb, "%s:", xml_namespace(x)); + if (modname) + cprintf(cb, "%s:", modname); cprintf(cb, "%s\": ", xml_name(x)); level++; cprintf(cb, "[%s%*s", @@ -368,7 +407,7 @@ xml2json1_cbuf(cbuf *cb, break; } /* Check for typed sub-body if: - * arracytype=* but chilt-type is BODY_CHILD + * arraytype=* but child-type is BODY_CHILD * This is code for writing 42 as "a":42 and not "a":"42" */ if (childt == BODY_CHILD && ys!=NULL && @@ -393,6 +432,8 @@ xml2json1_cbuf(cbuf *cb, for (i=0; i protocol operation, then the protocol operation is permitted. @@ -280,7 +265,7 @@ nacm_access(clicon_handle h, for (j=0; jietf-netconf:candidate", yspec, &xc) < 0) goto done; @@ -1005,6 +1000,12 @@ netconf_module_load(clicon_handle h) goto done; if (xml_parse_string("ietf-netconf:xpath", yspec, &xc) < 0) goto done; + + /* Load yang spec */ + if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) + goto done; + if (yang_spec_parse_module(h, "ietf-netconf-notification", NULL, yspec)< 0) + goto done; retval = 0; done: return retval; diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 74e5a645..873d3aaf 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -63,6 +63,7 @@ #include "clixon_log.h" #include "clixon_yang.h" #include "clixon_xml.h" +#include "clixon_xml_sort.h" #include "clixon_options.h" #include "clixon_plugin.h" #include "clixon_xpath_ctx.h" @@ -242,6 +243,9 @@ clicon_options_main(clicon_handle h, clicon_err(OE_CFG, 0, "%s: suffix %s not recognized (Run ./configure --with-config-compat?)", configfile, suffix); goto done; } +#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ + _CLICON_XML_NS_ITERATE = 1; +#endif /* Read configfile first without yangspec, for bootstrapping */ if (parse_configfile(h, configfile, yspec, &xconfig) < 0) goto done; @@ -267,6 +271,9 @@ clicon_options_main(clicon_handle h, xml_child_sort = 1; else xml_child_sort = 0; +#if 1 /* XXX Kludge to low-level functions to iterate over namspaces or not */ + _CLICON_XML_NS_ITERATE = clicon_option_bool(h, "CLICON_XML_NS_ITERATE"); +#endif retval = 0; done: return retval; diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index 9de878c0..0cf421d2 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -826,7 +826,8 @@ clicon_rpc_debug(clicon_handle h, char *username; username = clicon_username_get(h); - if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) + /* XXX: hardcoded example yang, should be clixon-config!!! */ + if ((msg = clicon_msg_encode("%d", username?username:"", level)) == NULL) goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 16183ef2..24bab41b 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -108,17 +108,13 @@ * - Namespace name: For a name N in a namespace identified by a URI I, the * "namespace name" is I. * For a name N that is not in a namespace, the "namespace name" has no value. - * - Local name: In either case the "local name" is N. + * - Local name: In either case the "local name" is N (also "prefix") * It is this combination of the universally managed URI namespace with the * vocabulary's local names that is effective in avoiding name clashes. */ struct xml{ char *x_name; /* name of node */ - char *x_namespace; /* namespace, if any */ -#ifdef notyet - char *x_namespacename; /* namespace name (or NULL) */ - char *x_localname; /* Local name N as defined above */ -#endif + char *x_prefix; /* namespace localname N, called prefix */ struct xml *x_up; /* parent node in hierarchy if any */ struct xml **x_childvec; /* vector of children nodes */ int x_childvec_len;/* length of vector */ @@ -130,6 +126,17 @@ struct xml{ reference, dont free */ }; +/* + * Variables + */ +/* Iterate through modules to find the matching datanode + * or rpc if no xmlns attribute specifies namespace. + * This is loose semantics of finding namespaces. + * And it is wrong, but is the way Clixon originally was written." + * @see CLICON_XML_NS_ITERATE clixon configure option + */ +int _CLICON_XML_NS_ITERATE = 0; + /* Mapping between xml type <--> string */ static const map_str2int xsmap[] = { {"error", CX_ERROR}, @@ -189,29 +196,31 @@ xml_name_set(cxobj *xn, /*! Get namespace of xnode * @param[in] xn xml node * @retval namespace of xml node + * XXX change to xml_localname */ char* xml_namespace(cxobj *xn) { - return xn->x_namespace; + return xn->x_prefix; } /*! Set name space of xnode, namespace is copied * @param[in] xn xml node - * @param[in] namespace new namespace, null-terminated string, copied by function + * @param[in] localname new namespace, null-terminated string, copied by function * @retval -1 on error with clicon-err set * @retval 0 OK + * XXX change to xml_localname_set */ int xml_namespace_set(cxobj *xn, - char *namespace) + char *localname) { - if (xn->x_namespace){ - free(xn->x_namespace); - xn->x_namespace = NULL; + if (xn->x_prefix){ + free(xn->x_prefix); + xn->x_prefix = NULL; } - if (namespace){ - if ((xn->x_namespace = strdup(namespace)) == NULL){ + if (localname){ + if ((xn->x_prefix = strdup(localname)) == NULL){ clicon_err(OE_XML, errno, "strdup"); return -1; } @@ -219,12 +228,57 @@ xml_namespace_set(cxobj *xn, return 0; } -/*! See if xmlns:= exists, if so return + +/*! Given an xml tree return URI namespace: default or localname given + * + * Given an XML tree and a prefix (or NULL) return URI namespace. + * @param[in] x XML tree + * @param[in] prefix prefix/ns localname. If NULL then return default. + * @param[out] namespace URI namespace (or NULL). Note pointer into xml tree + * @retval 0 OK + * @retval -1 Error + * @see xmlns_check XXX coordinate + */ +int +xml2ns(cxobj *x, + char *prefix, + char **namespace) +{ + int retval = -1; + char *ns; + cxobj *xp; + + if (prefix != NULL) /* xmlns: */ + ns = xml_find_type_value(x, "xmlns", prefix, CX_ATTR); + else /* default ns */ + ns = xml_find_type_value(x, NULL, "xmlns", CX_ATTR); + + /* namespace not found, try parent */ + if (ns == NULL){ + if ((xp = xml_parent(x)) != NULL){ + if (xml2ns(xp, prefix, &ns) < 0) + goto done; + } + /* If no parent, return default namespace if defined */ +#if defined(DEFAULT_XML_RPC_NAMESPACE) + else + ns = DEFAULT_XML_RPC_NAMESPACE; +#endif + } + if (namespace) + *namespace = ns; + retval = 0; + done: + return retval; +} + +/*! See if xmlns:[=] exists, if so return * * @param[in] xn XML node * @param[in] nsn Namespace name * @retval URI return associated URI if found * @retval NULL No namespace name binding found for nsn + * @see xml2ns XXX coordinate */ static char * xmlns_check(cxobj *xn, @@ -233,7 +287,7 @@ xmlns_check(cxobj *xn, cxobj *x = NULL; char *xns; - while ((x = xml_child_each(xn, x, -1)) != NULL) + while ((x = xml_child_each(xn, x, CX_ATTR)) != NULL) if ((xns = xml_namespace(x)) && strcmp(xns, "xmlns")==0 && strcmp(xml_name(x), nsn) == 0) return xml_value(x); @@ -248,7 +302,7 @@ xmlns_check(cxobj *xn, * @note This function is grossly inefficient */ static int -xml_namespace_check(cxobj *xn, +xml_localname_check(cxobj *xn, void *arg) { cxobj *xp = NULL; @@ -886,6 +940,42 @@ xml_body_get(cxobj *xt) return NULL; } +/*! Find and return the value of an xml child of specific type + * + * The value can be of an attribute only + * @param[in] xt xml tree node + * @param[in] prefix Prefix (namespace local name) or NULL + * @param[in] name name of xml tree node (eg attr name or "body") + * @retval val Pointer to the name string + * @retval NULL No such node or no value in node + * @code + * char *str = xml_find_type_value(x, "prefix", "name", CX_ATTR); + * @endcode + * @note, make a copy of the return value to use it properly + * @see xml_find_value where a body can be found as well + */ +char * +xml_find_type_value(cxobj *xt, + char *prefix, + char *name, + enum cxobj_type type) +{ + cxobj *x = NULL; + int pmatch; /* prefix match */ + char *xprefix; /* xprefix */ + + while ((x = xml_child_each(xt, x, type)) != NULL) { + xprefix = xml_namespace(x); + if (prefix) + pmatch = xprefix?strcmp(prefix,xprefix)==0:0; + else + pmatch = 1; + if (pmatch && strcmp(name, xml_name(x)) == 0) + return xml_value(x); + } + return NULL; +} + /*! Find and return the value of a sub xml node * * The value can be of an attribute or body. @@ -895,7 +985,7 @@ xml_body_get(cxobj *xt) * @retval NULL No such node or no value in node * * Note, make a copy of the return value to use it properly - * See also xml_find_body + * @see xml_find_body * Explaining picture: * xt --> x * x_name=name @@ -983,8 +1073,8 @@ xml_free(cxobj *x) free(x->x_name); if (x->x_value) free(x->x_value); - if (x->x_namespace) - free(x->x_namespace); + if (x->x_prefix) + free(x->x_prefix); for (i=0; ix_childvec_len; i++){ if ((xc = x->x_childvec[i]) != NULL){ xml_free(xc); @@ -1028,6 +1118,8 @@ clicon_xml2file(FILE *f, char *val; char *encstr = NULL; /* xml encoded string */ + if (x == NULL) + goto ok; name = xml_name(x); namespace = xml_namespace(x); switch(xml_type(x)){ @@ -1097,6 +1189,7 @@ clicon_xml2file(FILE *f, default: break; }/* switch */ + ok: retval = 0; done: if (encstr) @@ -1302,7 +1395,7 @@ _xml_parse(const char *str, if (clixon_xml_parseparse(&ya) != 0) /* yacc returns 1 on error */ goto done; /* Verify namespaces after parsing */ - if (xml_apply0(xt, CX_ELMNT, xml_namespace_check, NULL) < 0) + if (xml_apply0(xt, CX_ELMNT, xml_localname_check, NULL) < 0) goto done; /* Sort the complete tree after parsing */ if (yspec){ @@ -1507,15 +1600,20 @@ int xml_copy_one(cxobj *x0, cxobj *x1) { + char *s; + xml_type_set(x1, xml_type(x0)); - if (xml_value(x0)){ /* malloced string */ - if ((x1->x_value = strdup(x0->x_value)) == NULL){ + if ((s = xml_value(x0))){ /* malloced string */ + if ((x1->x_value = strdup(s)) == NULL){ clicon_err(OE_XML, errno, "strdup"); return -1; } } - if (xml_name(x0)) /* malloced string */ - if ((xml_name_set(x1, xml_name(x0))) < 0) + if ((s = xml_name(x0))) /* malloced string */ + if ((xml_name_set(x1, s)) < 0) + return -1; + if ((s = xml_namespace(x0))) /* malloced string */ + if ((xml_namespace_set(x1, s)) < 0) return -1; return 0; } @@ -1794,7 +1892,6 @@ xml_body_parse(cxobj *xb, if (retval < 0 && cv != NULL) cv_free(cv); return retval; - } /*! Parse an xml body as int32 diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 4dfe4f96..c3a4126a 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -70,7 +70,7 @@ /*! Load an xmldb storage plugin according to filename * If init function fails (not found, wrong version, etc) print a log and dont * add it. - * @param[in] h CLicon handle + * @param[in] h Clicon handle * @param[in] filename Actual filename including path */ int diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index d9c394cf..6794b503 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -245,7 +245,7 @@ validate_leafref(cxobj *xt, char *leafbody; if ((leafrefbody = xml_body(xt)) == NULL) - return 0; + goto ok; if ((ypath = yang_find((yang_node*)ytype, Y_PATH, NULL)) == NULL){ clicon_err(OE_DB, 0, "Leafref %s requires path statement", ytype->ys_argument); goto done; @@ -264,6 +264,7 @@ validate_leafref(cxobj *xt, leafrefbody); goto done; } + ok: retval = 0; done: if (xvec) @@ -337,6 +338,73 @@ validate_identityref(cxobj *xt, return retval; } +/*! Validate an RPC node + * @param[in] xt XML node to be validated + * @retval 1 Validation OK + * @retval 0 Validation failed + * @retval -1 Error + * rfc7950 + * 7.14.2 + * If a leaf in the input tree has a "mandatory" statement with the + * value "true", the leaf MUST be present in an RPC invocation. + * + * If a leaf in the input tree has a default value, the server MUST use + * this value in the same cases as those described in Section 7.6.1. In + * these cases, the server MUST operationally behave as if the leaf was + * present in the RPC invocation with the default value as its value. + * + * If a leaf-list in the input tree has one or more default values, the + * server MUST use these values in the same cases as those described in + * Section 7.7.2. In these cases, the server MUST operationally behave + * as if the leaf-list was present in the RPC invocation with the + * default values as its values. + * + * Since the input tree is not part of any datastore, all "config" + * statements for nodes in the input tree are ignored. + * + * If any node has a "when" statement that would evaluate to "false", + * then this node MUST NOT be present in the input tree. + * + * 7.14.4 + * Input parameters are encoded as child XML elements to the rpc node's + * XML element, in the same order as they are defined within the "input" + * statement. + * + * If the RPC operation invocation succeeded and no output parameters + * are returned, the contains a single element defined + * in [RFC6241]. If output parameters are returned, they are encoded as + * child elements to the element defined in [RFC6241], in + * the same order as they are defined within the "output" statement. + */ +int +xml_yang_validate_rpc(cxobj *xrpc) +{ + int retval = -1; + yang_stmt *yn=NULL; /* rpc name */ + cxobj *xn; /* rpc name */ + yang_stmt *yi=NULL; /* input name */ + cxobj *xi; /* input name */ + + assert(strcmp(xml_name(xrpc), "rpc")==0); + xn = NULL; + while ((xn = xml_child_each(xrpc, xn, CX_ELMNT)) != NULL) { + if ((yn = xml_spec(xn)) == NULL) + goto fail; + xi = NULL; + while ((xi = xml_child_each(xn, xi, CX_ELMNT)) != NULL) { + if ((yi = xml_spec(xi)) == NULL) + goto fail; + } + } + // ok: /* pass validation */ + retval = 1; + done: + return retval; + fail: + retval = 0; + goto done; +} + /*! Validate a single XML node with yang specification for added entry * 1. Check if mandatory leafs present as subs. * 2. Check leaf values, eg int ranges and string regexps. @@ -418,9 +486,14 @@ xml_yang_validate_add(cxobj *xt, /*! Validate a single XML node with yang specification for all (not only added) entries * 1. Check leafrefs. Eg you delete a leaf and a leafref references it. * @param[in] xt XML node to be validated - * @retval 0 Valid OK + * @param[in] arg Not used * @retval -1 Validation failed + * @retval 0 Validation OK * @see xml_yang_validate_add + * @code + * if (xml_apply(x, CX_ELMNT, (xml_applyfn_t*)xml_yang_validate_all, 0) < 0) + * err; + * @endcode */ int xml_yang_validate_all(cxobj *xt, @@ -1397,41 +1470,133 @@ xml_non_config_data(cxobj *xt, return retval; } - -/*! Add yang specification backpoint to XML node +/*! Add yang specification backpointer to rpc + * * @param[in] xt XML tree node * @param[in] arg Yang spec - * @note This may be unnecessary if yspec us set on creation + * @retval 0 OK + * @retval -1 Error + * @note This may be unnecessary if yspec is set on creation * @note For subs to anyxml nodes will not have spec set * @note No validation is done,... XXX * @code * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) * @endcode + * @see xml_spec_populate + */ +int +xml_spec_populate_rpc(clicon_handle h, + cxobj *xrpc, + yang_spec *yspec) +{ + int retval = -1; + yang_stmt *y=NULL; /* yang node */ + yang_stmt *ymod=NULL; /* yang module */ + yang_stmt *yi = NULL; /* input */ + yang_stmt *ya = NULL; /* arg */ + cxobj *x; + cxobj *xi; + int i; + + if ((strcmp(xml_name(xrpc), "rpc"))!=0){ + clicon_err(OE_UNIX, EINVAL, "RPC expected"); + goto done; + } + x = NULL; + while ((x = xml_child_each(xrpc, x, CX_ELMNT)) != NULL) { + if (ys_module_by_xml(yspec, x, &ymod) < 0) + goto done; + if (ymod != NULL) + y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x)); + /* Loose semantics: loop through all modules to find the node + */ + if (y == NULL && + clicon_option_bool(h, "CLICON_XML_NS_ITERATE")){ + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find((yang_node*)ymod, Y_RPC, xml_name(x))) != NULL) + break; + } + } + if (y){ + xml_spec_set(x, y); + if ((yi = yang_find((yang_node*)y, Y_INPUT, NULL)) != NULL){ + xi = NULL; + while ((xi = xml_child_each(x, xi, CX_ELMNT)) != NULL) { + if ((ya = yang_find_datanode((yang_node*)yi, xml_name(xi))) != NULL) + xml_spec_set(xi, ya); + } + } + } + } + retval = 0; + done: + return retval; +} + +/*! Add yang specification backpointer to XML node + * @param[in] xt XML tree node + * @param[in] arg Yang spec + * @note This may be unnecessary if yspec is set on creation + * @note For subs to anyxml nodes will not have spec set + * @note No validation is done,... XXX + * @note relies on kludge _CLICON_XML_NS_ITERATE + * @code + * xml_apply(xc, CX_ELMNT, xml_spec_populate, yspec) + * @endcode */ int xml_spec_populate(cxobj *x, void *arg) { int retval = -1; - yang_spec *yspec = (yang_spec*)arg; + // clicon_handle h = (clicon_handle)arg; + yang_spec *yspec=NULL; /* yang spec */ yang_stmt *y=NULL; /* yang node */ + yang_stmt *yparent; /* yang parent */ + yang_stmt *ymod; /* yang module */ + cxobj *xp; /* xml parent */ + char *name; + int i; - if (xml_child_spec(xml_name(x), xml_parent(x), yspec, &y) < 0) - goto done; -#if 0 - if ((xp = xml_parent(x)) != NULL && - (yp = xml_spec(xp)) != NULL) - y = yang_find_datanode((yang_node*)yp, xml_name(x)); - else - y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ -#endif - if (y) + yspec = (yang_spec*)arg; + if (xml_spec(x)) + goto ok; + xp = xml_parent(x); + name = xml_name(x); + if (xp && (yparent = xml_spec(xp)) != NULL) + y = yang_find_datanode((yang_node*)yparent, name); + else if (yspec){ + if (ys_module_by_xml(yspec, x, &ymod) < 0) + goto done; + if (ymod != NULL) + y = yang_find_datanode((yang_node*)ymod, name); + /* Loose semantics: loop through all modules to find the node + * XXX clicon_option_bool(h, "CLICON_XML_NS_ITERATE") + */ + if (y == NULL && _CLICON_XML_NS_ITERATE){ + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_datanode((yang_node*)ymod, name)) != NULL) + break; + } + } + } + if (y) xml_spec_set(x, y); +#if 0 /* Add if you want validation error */ + else { + clicon_err(OE_YANG, ENOENT, "No yang top found?"); + goto done; + } +#endif + ok: retval = 0; done: return retval; } + /*! Translate from restconf api-path in cvv form to xml xpath * eg a/b=c -> a/[b=c] * @param[in] yspec Yang spec @@ -1757,7 +1922,6 @@ api_path2xml(char *api_path, * @retval 0 OK. If reason is set, Yang error * @retval -1 Error * Assume x0 and x1 are same on entry and that y is the spec - * @see put in clixon_keyvalue.c */ static int xml_merge1(cxobj *x0, diff --git a/lib/src/clixon_xml_sort.c b/lib/src/clixon_xml_sort.c index 27842b6a..7e68d2b8 100644 --- a/lib/src/clixon_xml_sort.c +++ b/lib/src/clixon_xml_sort.c @@ -56,7 +56,6 @@ #include "clixon_err.h" #include "clixon_log.h" #include "clixon_string.h" - #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" @@ -73,7 +72,6 @@ */ int xml_child_sort = 1; - /*! Given a child name and an XML object, return yang stmt of child * If no xml parent, find root yang stmt matching name * @param[in] x Child @@ -81,6 +79,8 @@ int xml_child_sort = 1; * @param[in] yspec Yang specification (top level) * @param[out] yresult Pointer to yang stmt of result, or NULL, if not found * @note special rule for rpc, ie ,look for top "foo" node. + * @note works for import prefix, but not work for generic XML parsing where + * xmlns and xmlns:ns are used. */ int xml_child_spec(char *name, @@ -88,17 +88,40 @@ xml_child_spec(char *name, yang_spec *yspec, yang_stmt **yresult) { - yang_stmt *y; /* result yang node */ + int retval = -1; + yang_stmt *y = NULL; /* result yang node */ yang_stmt *yparent; /* parent yang */ - - if (xp && (yparent = xml_spec(xp)) != NULL) - y = yang_find_datanode((yang_node*)yparent, name); - else if (yspec) - y = yang_find_topnode(yspec, name, YC_DATANODE); /* still NULL for config */ + yang_stmt *ymod = NULL; + yang_stmt *yi; + int i; + + if (xp && (yparent = xml_spec(xp)) != NULL){ + if (yparent->ys_keyword == Y_RPC){ + if ((yi = yang_find((yang_node*)yparent, Y_INPUT, NULL)) != NULL) + y = yang_find_datanode((yang_node*)yi, name); + } + else + y = yang_find_datanode((yang_node*)yparent, name); + } + else if (yspec){ + if (ys_module_by_xml(yspec, xp, &ymod) < 0) + goto done; + if (ymod != NULL) + y = yang_find_schemanode((yang_node*)ymod, name); + if (y == NULL && _CLICON_XML_NS_ITERATE){ + for (i=0; iyp_len; i++){ + ymod = yspec->yp_stmt[i]; + if ((y = yang_find_schemanode((yang_node*)ymod, name)) != NULL) + break; + } + } + } else y = NULL; *yresult = y; - return 0; + retval = 0; + done: + return retval; } /*! Help function to qsort for sorting entries in xml child vector diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 2a6b1572..8b9af6d0 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -658,15 +658,18 @@ yang_find_schemanode(yang_node *yn, return ysmatch; } -/*! Find first matching data node in all (sub)modules in a yang spec +/*! Find first matching data node in all modules in a yang spec (prefixes) * * @param[in] ysp Yang specification - * @param[in] argument Name of node. If NULL match first + * @param[in] nodeid Name of node. If NULL match first * @param[in] class See yang_class for class of yang nodes * A yang specification has modules as children which in turn can have * syntax-nodes as children. This function goes through all the modules to * look for nodes. Note that if a child to a module is a choice, * the search is made recursively made to the choice's children. + * @note works for import prefix, but not work for generic XML parsing where + * xmlns and xmlns:ns are used. + * @see yang_find_top_ns */ yang_stmt * yang_find_topnode(yang_spec *ysp, @@ -677,7 +680,7 @@ yang_find_topnode(yang_spec *ysp, yang_stmt *yres = NULL; /* result */ char *prefix = NULL; char *id = NULL; - int i; + int i; if (yang_nodeid_split(nodeid, &prefix, &id) < 0) goto done; @@ -719,7 +722,7 @@ yang_find_topnode(yang_spec *ysp, } /*! Given a yang statement, find the prefix associated to this module - * @param[in] ys Yang statement + * @param[in] ys Yang statement in module tree (or module itself) * @retval NULL Not found * @retval prefix Prefix as char* pointer into yang tree * @code @@ -745,6 +748,34 @@ yang_find_myprefix(yang_stmt *ys) return prefix; } +/*! Given a yang statement, find the namespace URI associated to this module + * @param[in] ys Yang statement in module tree (or module itself) + * @retval NULL Not found + * @retval namespace Namspace URI as char* pointer into yang tree + * @code + * char *myns = yang_find_mynamespace(ys); + * @endcode + * @see yang_find_module_by_namespace + */ +char * +yang_find_mynamespace(yang_stmt *ys) +{ + yang_stmt *ymod; /* My module */ + yang_stmt *ynamespace; + char *namespace = NULL; + + if ((ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang module not found"); + goto done; + } + if ((ynamespace = yang_find((yang_node*)ymod, Y_NAMESPACE, NULL)) == NULL) + goto done; + namespace = ynamespace->ys_argument; + done: + return namespace; +} + + /*! Find matching y in yp:s children, return 0 and index or -1 if not found. * @retval 0 not found * @retval 1 found @@ -811,7 +842,52 @@ yang_key2str(int keyword) return (char*)clicon_int2str(ykmap, keyword); } -/*! Find top module or sub-module given a statement. +/*! Find top data node among all modules by namespace in xml tree + * @param[in] ysp Yang specification + * @param[in] xt XML node + * @param[out] ymod Yang module (NULL if not found) + * @retval 0 OK + * @retval -1 Error + * @note works for xml namespaces (xmlns / xmlns:ns) + */ +int +ys_module_by_xml(yang_spec *ysp, + cxobj *xt, + yang_stmt **ymodp) +{ + int retval = -1; + yang_stmt *ym = NULL; /* module */ + char *prefix = NULL; + char *namespace = NULL; /* namespace URI */ + + if (ymodp) + *ymodp = NULL; + prefix = xml_namespace(xt); + if (prefix){ + /* Get namespace for prefix */ + if (xml2ns(xt, prefix, &namespace) < 0) + goto done; + } + else{ + /* Get default namespace */ + if (xml2ns(xt, NULL, &namespace) < 0) + goto done; + } + /* No namespace found, give up */ + if (namespace == NULL) + goto ok; + /* We got the namespace, now get the module */ + ym = yang_find_module_by_namespace(ysp, namespace); + /* Set result param */ + if (ymodp && ym) + *ymodp = ym; + ok: + retval = 0; + done: + return retval; +} + +/*! Find the top module or sub-module given a statement from within a yang tree * Ultimate top is yang spec, dont return that * The routine recursively finds ancestors. * @param[in] ys Any yang statement in a yang tree @@ -840,7 +916,7 @@ ys_module(yang_stmt *ys) return ys; } -/*! Find top of tree, the yang specification +/*! Find top of tree, the yang specification from within the tree * @param[in] ys Any yang statement in a yang tree * @retval yspec The top yang specification * @see ys_module @@ -1005,6 +1081,29 @@ yang_find_module_by_prefix(yang_stmt *ys, return ymod; } +/*! Given a yang statement and a namespace, return yang module + * + * @param[in] yspec A yang specification + * @param[in] namespace namespace + * @retval ymod Yang module statement if found + * @retval NULL not found + */ +yang_stmt * +yang_find_module_by_namespace(yang_spec *yspec, + char *namespace) +{ + yang_stmt *ymod = NULL; + + if (namespace == NULL) + goto done; + while ((ymod = yn_each((yang_node*)yspec, ymod)) != NULL) { + if (yang_find((yang_node*)ymod, Y_NAMESPACE, namespace) != NULL) + break; + } + done: + return ymod; +} + /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) @@ -1984,6 +2083,7 @@ yang_parse_filename(const char *filename, int fd = -1; struct stat st; + // clicon_debug(1, "%s %s", __FUNCTION__, filename); if (stat(filename, &st) < 0){ clicon_err(OE_YANG, errno, "%s not found", filename); goto done; @@ -2903,6 +3003,8 @@ yang_spec_load_dir(clicon_handle h, len = b-base; else len = strlen(base); + /* remove duplicates: there may be cornercases that dont work, eg + * mix of revisions and not? */ for (j = (i+1); j < ndp; j++) if (strncmp(base, dp[j].d_name, len) == 0) break; diff --git a/test/lib.sh b/test/lib.sh index eb6bdbbd..2a66d2d5 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -7,6 +7,12 @@ testnr=0 testname= +# If set to 0, override starting of clixon_backend in test (you bring your own) +: ${BE:=1} + +# If set, enable debugging (of backend) +: ${DBG:=0} + # For memcheck #clixon_cli="valgrind --leak-check=full --show-leak-kinds=all clixon_cli" clixon_cli=clixon_cli @@ -56,6 +62,7 @@ new(){ testname=$1 >&2 echo "Test$testnr [$1]" } +# No CR new2(){ testnr=`expr $testnr + 1` testname=$1 @@ -84,7 +91,7 @@ expectfn(){ # echo "retval:\"$retval\"" # echo "ret:\"$ret\"" # echo "r:\"$r\"" - if [ $r != $retval ]; then + if [ $r != $retval ]; then echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" echo -e "\e[0m:" exit -1 @@ -219,3 +226,29 @@ expectwait(){ fi } +expectmatch(){ + ret=$1 + r=$2 + expret=$3 + expect=$4 + if [ $r != $expret ]; then + echo -e "\e[31m\nError ($r != $retval) in Test$testnr [$testname]:" + echo -e "\e[0m:" + exit -1 + fi + if [ -z "$ret" -a -z "$expect" ]; then + echo > /dev/null + else + match=$(echo "$ret" | grep -Eo "$expect") + if [ -z "$match" ]; then + err "$expect" "$ret" + fi + if [ -n "$expect2" ]; then + match=`echo "$ret" | grep -EZo "$expect2"` + if [ -z "$match" ]; then + err $expect "$ret" + fi + fi + fi + +} diff --git a/test/nacm.sh b/test/nacm.sh new file mode 100755 index 00000000..01e7ad05 --- /dev/null +++ b/test/nacm.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# Library variable and functions + +USER=$(whoami) + +# Three groups from RFC8341 A.1 (admin extended with $USER) +NGROUPS=$(cat < + + admin + admin + andy + $USER + + + limited + wilma + bam-bam + + + guest + guest + guest@example.com + + +EOF +) + +# Permit all rule for admin group from RFC8341 A.2 +NADMIN=$(cat < + admin-acl + admin + + permit-all + * + * + permit + + Allow the 'admin' group complete access to all operations and data. + + + +EOF +) + diff --git a/test/test_cli.sh b/test/test_cli.sh index a93cf0fb..3bf4af77 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -14,7 +14,7 @@ APPNAME=example cfg=$dir/conf_yang.xml cat < $cfg - + $cfg /usr/local/share/$APPNAME/yang /usr/local/share/clixon @@ -31,17 +31,18 @@ cat < $cfg EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -z -f $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg" -sudo $clixon_backend -s init -f $cfg - -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "cli configure top" @@ -114,6 +115,10 @@ expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 "ipv4" "2.3.4.5" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_datastore.sh b/test/test_datastore.sh index 122b3add..bcc7b27b 100755 --- a/test/test_datastore.sh +++ b/test/test_datastore.sh @@ -46,7 +46,12 @@ module ietf-ip{ } EOF -db='12first-entry13second-entry23third-entryabcastring' + + +xml='12first-entry13second-entry23third-entryabcastring' + +# Without xmlns +xmlxxx='12first-entry13second-entry23third-entryabcastring' run(){ name=$1 @@ -62,12 +67,12 @@ run(){ new "datastore $name init" expectfn "$datastore $conf init" 0 "" - # Whole tree operations new "datastore $name put all replace" - expectfn "$datastore $conf put replace $db" 0 "" + ret=$($datastore $conf put replace "$xml") + expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$db$" + expectfn "$datastore $conf get /" 0 "^$xmlxxx$" new "datastore $name put all remove" expectfn "$datastore $conf put remove " 0 "" @@ -76,10 +81,13 @@ run(){ expectfn "$datastore $conf get /" 0 "^$" new "datastore $name put all merge" - expectfn "$datastore $conf put merge $db" 0 "" + ret=$($datastore $conf put merge "$xml") + expectmatch "$ret" $? "0" "" + +# expectfn "$datastore $conf put merge $xml" 0 "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$db$" + expectfn "$datastore $conf get /" 0 "^$xmlxxx$" new "datastore $name put all delete" expectfn "$datastore $conf put remove " 0 "" @@ -88,10 +96,11 @@ run(){ expectfn "$datastore $conf get /" 0 "^$" new "datastore $name put all create" - expectfn "$datastore $conf put create $db" 0 "" + ret=$($datastore $conf put create "$xml") + expectmatch "$ret" $? "0" "" new "datastore $name get" - expectfn "$datastore $conf get /" 0 "^$db$" + expectfn "$datastore $conf get /" 0 "^$xmlxxx$" new "datastore $name put top create" expectfn "$datastore $conf put create " 0 "" # error @@ -159,7 +168,6 @@ run(){ rm -rf $mydir } -#run keyvalue # cant get the put to work run text rm -rf $dir diff --git a/test/test_feature.sh b/test/test_feature.sh index 86f3ffd2..4bbb334c 100755 --- a/test/test_feature.sh +++ b/test/test_feature.sh @@ -57,19 +57,21 @@ module $APPNAME{ } } EOF -new "start backend -s init -f $cfg -y $fyang" -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "cli enabled feature" @@ -94,7 +96,8 @@ new "netconf validate enabled feature" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf disabled feature" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedapplicationerrorValidation failed]]>]]>$' +#expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "foo]]>]]>" '^operation-failedprotocolerrorXML node config/A has no corresponding yang specification (Invalid XML or wrong Yang spec?' # This test has been broken up into all different modules instead of one large # reply since the modules change so often @@ -134,7 +137,7 @@ if [ -z "$match" ]; then fi new "netconf module ietf-netconf" -expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0implement" +expect="module>ietf-netconf2011-06-01urn:ietf:params:xml:ns:netconf:base:1.0candidatestartupvalidatexpathimplement" match=`echo "$ret" | grep -GZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -159,6 +162,10 @@ if [ -z "$match" ]; then err "$expect" "$ret" fi +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_identity.sh b/test/test_identity.sh index f9498013..1acbc764 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -106,21 +106,23 @@ cat < $fyang } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "Set crypto to aes" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "aes]]>]]>" "^]]>]]>$" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'aes]]>]]>' '^]]>]]>$' new "netconf validate " expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -180,6 +182,10 @@ expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o set crypto des:des3" 0 "^$" new "cli validate" expectfn "$clixon_cli -1 -f $cfg -y $fyang -l o validate" 0 "^$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_leafref.sh b/test/test_leafref.sh index 719a77e6..72faaa4e 100755 --- a/test/test_leafref.sh +++ b/test/test_leafref.sh @@ -72,18 +72,19 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -# start new backend -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "leafref base config" @@ -142,6 +143,10 @@ expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender a" 0 "^$" new "cli sender template" expectfn "$clixon_cli -1f $cfg -y $fyang -l o set sender b template a" 0 "^$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_list.sh b/test/test_list.sh index ff12015a..7e42a23b 100755 --- a/test/test_list.sh +++ b/test/test_list.sh @@ -64,18 +64,20 @@ module $APPNAME{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "minmax: minimal" @@ -123,6 +125,10 @@ new "minmax: validate should fail" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" fi # NYI +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_nacm.sh b/test/test_nacm.sh index afac1df7..c22159c2 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -6,6 +6,7 @@ APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh +. ./nacm.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang @@ -45,30 +46,17 @@ module $APPNAME{ } EOF +# The groups are slightly modified from RFC8341 A.1 +# The rule-list is from A.2 RULES=$(cat < false deny deny deny - - - admin - admin - adm1 - olof - - - limited - wilma - bam-bam - - - guest - guest - guest@example.com - - + + $NGROUPS + guest-acl guest @@ -78,7 +66,8 @@ RULES=$(cat <* deny - Do not allow guests any access to any information. + Do not allow guests any access to the NETCONF + monitoring information. @@ -106,36 +95,27 @@ RULES=$(cat < - - admin-acl - admin - - permit-all - * - * - permit - - Allow the 'admin' group complete access to all operations and data. - - - + + $NADMIN + 0 EOF ) -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -148,10 +128,10 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & sleep $RCWAIT new "restconf DELETE whole datastore" -expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" +expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" 'null +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null ' new "auth set authentication config" @@ -164,19 +144,19 @@ new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' #----------------Enable NACM new "enable nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" new2 "admin get nacm" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' new2 "limited get nacm" @@ -184,20 +164,24 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 997965ff..1db3949f 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -7,6 +7,7 @@ APPNAME=example # include err() and new() functions and creates $dir . ./lib.sh +. ./nacm.sh cfg=$dir/conf_yang.xml fyang=$dir/test.yang @@ -40,7 +41,12 @@ cat < $fyang module $APPNAME{ yang-version 1.1; namespace "urn:example:clixon"; + prefix ex; + import ietf-routing { + description "For fib-route"; + prefix rt; + } container authentication { description "Example code for enabling www basic auth and some example users"; @@ -82,23 +88,9 @@ cat < $nacmfile deny deny deny - - - admin - admin - adm1 - - - limited - wilma - bam-bam - - - guest - guest - guest@example.com - - + + $NGROUPS + guest-acl guest @@ -136,34 +128,27 @@ cat < $nacmfile - - admin-acl - admin - - permit-all - * - * - permit - - Allow the 'admin' group complete access to all operations and data. - - - + + $NADMIN + EOF -# kill old backend (if any) -new "kill old backend -zf $cfg -y $fyang" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi -sleep 1 -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend -zf $cfg -y $fyang" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + sleep 1 + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -175,27 +160,27 @@ sudo su -c "$clixon_restconf -f $cfg -y $fyang -- -a" -s /bin/sh www-data & sleep $RCWAIT new "restconf DELETE whole datastore" -expecteq "$(curl -u adm1:bar -sS -X DELETE http://localhost/restconf/data)" "" +expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" "" new2 "auth get" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/state)" '{"state": {"op": "42"}} ' new "Set x to 0" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 0}' http://localhost/restconf/data/x)" "" new2 "auth get (no user: access denied)" expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (wrong passwd: access denied)" -expecteq "$(curl -u adm1:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' new2 "auth get (access)" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' new2 "admin get nacm" -expecteq "$(curl -u adm1:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} ' new2 "limited get nacm" @@ -203,19 +188,19 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/x)" '{"x ' new2 "guest get nacm" -expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "admin edit nacm" -expecteq "$(curl -u adm1:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"x": 1}' http://localhost/restconf/data/x)" "" new2 "limited edit nacm" expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' new2 "guest edit nacm" -expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "The requested URL was unauthorized"}}} ' +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "access denied"}}} ' new "cli show conf as admin" -expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" +expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" new "cli show conf as limited" expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang show conf" 0 "^x 1;$" @@ -224,7 +209,7 @@ new "cli show conf as guest" expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied" new "cli rpc as admin" -expectfn "$clixon_cli -1 -U adm1 -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" +expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 "2.3.4.5" new "cli rpc as limited" expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny" @@ -235,6 +220,10 @@ expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh new file mode 100755 index 00000000..2ab70b4b --- /dev/null +++ b/test/test_nacm_protocol.sh @@ -0,0 +1,231 @@ +#!/bin/bash +# Authentication and authorization and IETF NACM +# NACM protocol operation rules +# @see RFC 8341 A.1 and A.3 (and permit-all from A.2) +# Tests for three protocol operation rules (all apply to module ietf-netconf) +# deny-kill-session: This rule prevents the "limited" group or the +# "guest" group from invoking the NETCONF protocol +# operation. +# deny-delete-config: This rule prevents the "limited" group or the +# "guest" group from invoking the NETCONF protocol +# operation. +# permit-edit-config: This rule allows the "limited" group to invoke +# the NETCONF protocol operation. This rule will have +# no real effect unless the "exec-default" leaf is set to "deny". +# +# From RFC8040, I conclude that commit/discard should be done automatically +# BY THE SYSTEM +# Otherwise, if the device supports :candidate, all edits to +# configuration nodes in {+restconf}/data are performed in the +# candidate configuration datastore. The candidate MUST be +# automatically committed to running immediately after each successful +# edit. +# Which means that restconf -X DELETE /data translates to edit-config + commit +# WHICH IS allowed. + +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh +. ./nacm.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/test.yang +fyangerr=$dir/err.yang + +cat < $cfg + + $cfg + /usr/local/share/clixon + /usr/local/share/clixon + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + false + internal + +EOF + +cat < $fyang +module $APPNAME{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import ietf-netconf-acm { + prefix nacm; + } + leaf x{ + type int32; + description "something to edit"; + } +} +EOF + +# The groups are slightly modified from RFC8341 A.1 +# The rule-list is from A.2 +RULES=$(cat < + false + deny + deny + deny + + $NGROUPS + + + guest-limited-acl + limited + guest + + deny-kill-session + ietf-netconf + kill-session + exec + deny + + Do not allow the 'limited' group or the 'guest' group + to kill another session. + + + + deny-delete-config + ietf-netconf + delete-config + exec + deny + + Do not allow the 'limited' group or the 'guest' group + to delete any configurations. + + + + + limited-acl + limited + + permit-edit-config + ietf-netconf + edit-config + exec + permit + + Allow the 'limited' group to edit the configuration. + + + + + $NADMIN + + + 0 +EOF +) + +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi +fi + +new "kill old restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +sleep 1 +new "start restconf daemon (-a is enable basic authentication)" +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1 -- -a" -s /bin/sh www-data & + +sleep $RCWAIT + +new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$RULES]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "enable nacm" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +new2 "admin get nacm" +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" '{"x": 0} + ' + +# Rule 1: deny-kill-session +new "deny-kill-session: limited fail (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" + +new "deny-kill-session: guest fail (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "44]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" + +new "deny-kill-session: admin ok (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "44]]>]]>" "^]]>]]>$" + +# Rule 2: deny-delete-config +new "deny-delete-config: limited fail (netconf)" +expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "]]>]]>" "^access-deniedprotocolerroraccess denied]]>]]>$" + +new2 "deny-delete-config: guest fail (restconf)" +expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +# In restconf delete-config is translated to edit-config which is permitted +new "deny-delete-config: limited fail (restconf) ok" +expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" '' + +new2 "admin get nacm (should be null)" +expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/x)" 'null + ' + +new "deny-delete-config: admin ok (restconf)" +expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" '' + +# Here the whole config is gone so we need to start again +new "auth set authentication config (restart)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "$RULES]]>]]>" "^]]>]]>$" + +new "commit it" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "enable nacm" +expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/nacm/enable-nacm)" "" + +# Rule 3: permit-edit-config +new "permit-edit-config: limited ok restconf" +expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '' + +new2 "permit-edit-config: guest fail restconf" +expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/x)" '{"ietf-restconf:errors" : {"error": {"error-tag": "access-denied","error-type": "protocol","error-severity": "error","error-message": "default deny"}}} ' + +new "Kill restconf daemon" +sudo pkill -u www-data -f "/www-data/clixon_restconf" + +if [ $BE -ne 0 ]; then + exit # BE +fi + +new "Kill backend" +# Check if premature kill +pid=`pgrep -u root -f clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +#rm -rf $dir # XXX diff --git a/test/test_netconf.sh b/test/test_netconf.sh index e66aae53..a0eae7d8 100755 --- a/test/test_netconf.sh +++ b/test/test_netconf.sh @@ -87,17 +87,21 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +# Bring your own backend +if [ $BE -ne 0 ]; then + # kill old backend (if any) + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "netconf hello" @@ -222,6 +226,7 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +# XXX NOTE that this does not actually kill a running session - and may even kill some random process,... new "kill-session" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "44]]>]]>" "^]]>]]>$" @@ -238,10 +243,10 @@ new "netconf check empty startup" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf rpc" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 'ipv4ipv4]]>]]>' "^ipv4" -new "netconf rpc without namespace" -expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" +new "netconf rpc without namespace (iterate kludge should work)" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "ipv4ipv4]]>]]>" "^ipv4" new "netconf empty rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" @@ -249,6 +254,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" new "netconf client-side rpc" expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "example]]>]]>" "^ok]]>]]>$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_order.sh b/test/test_order.sh index a44586cd..a06a036b 100755 --- a/test/test_order.sh +++ b/test/test_order.sh @@ -107,18 +107,19 @@ cat < $dbdir/running_db EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend" -# start new backend -sudo $clixon_backend -s running -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend" + sudo $clixon_backend -s running -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi # Check as file @@ -171,6 +172,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^cbarbfooafie]]>]]>$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_perf.sh b/test/test_perf.sh index a2c36cf8..d03fcbde 100755 --- a/test/test_perf.sh +++ b/test/test_perf.sh @@ -12,7 +12,7 @@ elif [ $# = 2 ]; then req=$2 else echo "Usage: $0 [ []]" - exit 1 + exit 1 # Scaling fi APPNAME=example # include err() and new() functions and creates $dir @@ -58,18 +58,20 @@ cat < $cfg EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg" -y $fyang +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err + new "start backend -s init -f $cfg -y $fyang" + # start new backend + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -103,10 +105,10 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf commit large config again" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add small (1 entry) config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "xy]]>]]>" "^]]>]]>$" @@ -151,7 +153,7 @@ expecteof_file "time -f %e $clixon_netconf -qf $cfg -y $fyang" "$fconfig" "^]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf add $req small leaf-list config" time -p for (( i=0; i<$req; i++ )); do @@ -163,7 +165,7 @@ new "netconf add small leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "x]]>]]>" "^]]>]]>$" new "netconf commit small leaf-list config" -expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" new "netconf get large leaf-list config" expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^01" @@ -171,6 +173,10 @@ expecteof "time -f %e $clixon_netconf -qf $cfg -y $fyang" 0 "< new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_restconf.sh b/test/test_restconf.sh index f3b8fa72..f98b87e4 100755 --- a/test/test_restconf.sh +++ b/test/test_restconf.sh @@ -86,23 +86,25 @@ EOF # This is a fixed 'state' implemented in routing_backend. It is assumed to be always there state='{"state": {"op": "42"}}' -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" sudo pkill -u www-data clixon_restconf new "start restconf daemon" -sudo su -c "$clixon_restconf -f $cfg -y $fyang" -s /bin/sh www-data & +sudo su -c "$clixon_restconf -f $cfg -y $fyang -D 1" -s /bin/sh www-data & sleep $RCWAIT @@ -128,7 +130,7 @@ expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex new "restconf get restconf/operations. RFC8040 3.3.2 (xml)" ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/operations) -expect="" +expect='' match=`echo $ret | grep -EZo "$expect"` if [ -z "$match" ]; then err "$expect" "$ret" @@ -157,7 +159,7 @@ expectfn "curl -s -I http://localhost/restconf/data" 0 "HTTP/1.1 200 OK" #Content-Type: application/yang-data+json" new "restconf empty rpc" -expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/example:empty)" "" +expecteq "$(curl -s -X POST -d {\"input\":null} http://localhost/restconf/operations/example:empty)" "" new2 "restconf get empty config + state json" expecteq "$(curl -sSG http://localhost/restconf/data/state)" '{"state": {"op": "42"}} @@ -319,6 +321,10 @@ fi new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_restconf2.sh b/test/test_restconf2.sh index 938aa232..dbddee09 100755 --- a/test/test_restconf2.sh +++ b/test/test_restconf2.sh @@ -46,16 +46,19 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -140,6 +143,10 @@ expectfn 'curl -is -X PUT -d {"interface":{"name":"ALPHA","type":"eth0"}} http:/ new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_stream.sh b/test/test_stream.sh index ce0aefc5..910a29c8 100755 --- a/test/test_stream.sh +++ b/test/test_stream.sh @@ -102,16 +102,19 @@ cat < $fyang } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang # -D 1 -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "kill old restconf daemon" @@ -275,6 +278,10 @@ sleep 5 new "Kill restconf daemon" sudo pkill -u www-data -f "/www-data/clixon_restconf" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_type.sh b/test/test_type.sh index d23d13cb..4ff09bbc 100755 --- a/test/test_type.sh +++ b/test/test_type.sh @@ -192,16 +192,19 @@ module example{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi fi new "cli set transitive string" @@ -300,6 +303,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 " $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + example + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + +EOF + +# transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang +cat < $fyang3 +module example3{ + prefix ex3; + namespace "urn:example:example3"; + typedef u{ + type union { + type int32{ + range "4..44"; + } + type enumeration { + enum "unbounded"; + } + } + } + typedef t{ + type string; + } +} +EOF +cat < $fyang2 +module example2{ + import example3 { prefix ex3; } + namespace "urn:example:example2"; + prefix ex2; + grouping gr2 { + leaf talle{ + type ex3:t; + } + leaf ulle{ + type ex3:u; + } + } +} +EOF +cat < $fyang +module example{ + yang-version 1.1; + namespace "urn:example:clixon"; + prefix ex; + import example2 { prefix ex2; } + container c{ + description "transitive type- exists in ex3"; + uses ex2:gr2; + } +} +EOF + +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi +fi + +new "cli set transitive string" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c talle x" 0 "^$" + +new "cli set transitive union" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle 33" 0 "^$" + +new "cli set transitive union error" +expectfn "$clixon_cli -1f $cfg -l o -y $fyang set c ulle kalle" 255 "" + +if [ $BE -ne 0 ]; then + exit # BE +fi + +new "Kill backend" +# Check if premature kill +pid=`pgrep -u root -f clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi + +rm -rf $dir diff --git a/test/test_when_must.sh b/test/test_when_must.sh index f83edd78..6e3b971f 100755 --- a/test/test_when_must.sh +++ b/test/test_when_must.sh @@ -90,18 +90,19 @@ module $APPNAME{ } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi +new "test params: -f $cfg -y $fyang" -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "when: add static route" @@ -140,6 +141,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^operation-failedapplicationerrorAn Ethernet MTU must be 1500]]>]]>" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_yang.sh b/test/test_yang.sh index 82b5bba2..9a3f7cfe 100755 --- a/test/test_yang.sh +++ b/test/test_yang.sh @@ -13,7 +13,6 @@ fyangerr=$dir/err.yang cat < $cfg $cfg - $dir /usr/local/share/clixon $APPNAME @@ -141,18 +140,20 @@ module $APPNAME{ ex:not-defined ARGUMENT; } EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -y $fyang -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg -y $fyang" -# start new backend -sudo $clixon_backend -s init -f $cfg -y $fyang -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg -y $fyang" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg -y $fyang + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg -y $fyang" + sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "cli defined extension" @@ -278,6 +279,10 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" +if [ $BE -ne 0 ]; then + exit # BE +fi + new "Kill backend" # Check if premature kill pid=`pgrep -u root -f clixon_backend` diff --git a/test/test_yang_load.sh b/test/test_yang_load.sh index ae4f56e3..9bcae5fa 100755 --- a/test/test_yang_load.sh +++ b/test/test_yang_load.sh @@ -69,28 +69,33 @@ cat < $cfg 1 EOF -# kill old backend (if any) -new "kill old backend" -sudo clixon_backend -zf $cfg -if [ $? -ne 0 ]; then - err -fi -new "start backend -s init -f $cfg" -# start new backend -sudo $clixon_backend -s init -f $cfg -if [ $? -ne 0 ]; then - err +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi fi new "1. Set newex" expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^]]>]]>$' -new "Set oldex should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +new "Set oldex should fail (since oldex is in old revision and only the new is loaded)" +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' new "Set other should fail" -expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedprotocolerrorXML' +expecteof "$clixon_netconf -qf $cfg" 0 'str]]>]]>' '^operation-failedapplicationerrorValidation failed]]>]]>$' + +if [ $BE -ne 0 ]; then + exit # BE +fi new "Kill backend" # Check if premature kill diff --git a/test/test_yang_namespace.sh b/test/test_yang_namespace.sh new file mode 100755 index 00000000..215e91f3 --- /dev/null +++ b/test/test_yang_namespace.sh @@ -0,0 +1,141 @@ +#!/bin/bash +# Yang specifics: multi-keys and empty type +APPNAME=example +# include err() and new() functions and creates $dir +. ./lib.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/example.yang +fyang2=$dir/example2.yang + +# /usr/local/share/$APPNAME/yang +cat < $cfg + + $cfg + $dir + /usr/local/share/$APPNAME/yang + /usr/local/share/clixon + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + 1 + /usr/local/var/$APPNAME + /usr/local/lib/xmldb/text.so + true + +EOF + +# For testing namespaces - +# x.y is different type. Here it is string whereas in fyang it is list. +# +cat < $fyang2 +module example2{ + yang-version 1.1; + prefix ex2; + namespace "urn:example:clixon2"; + container x { + leaf y { + type uint32; + } + } +} +EOF + +cat < $fyang +module example{ + yang-version 1.1; + prefix ex; + namespace "urn:example:clixon"; + import ietf-routing { + description "defines fib-route"; + prefix rt; + } + leaf x{ + type int32; + } + rpc client-rpc { + description "Example local client-side RPC that is processed by the + the netconf/restconf and not sent to the backend. + This is a clixon implementation detail: some rpc:s + are better processed by the client for API or perf reasons"; + input { + leaf request { + type string; + } + } + output { + leaf result{ + type string; + } + } + } +} +EOF + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + + new "start backend -s init -f $cfg" + # start new backend + sudo $clixon_backend -s init -f $cfg -D $DBG + if [ $? -ne 0 ]; then + err + fi +fi + + + +new "netconf xmlns module ex" +expecteof "$clixon_netconf -qf $cfg" 0 '42]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlfn in return" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^42]]>]]>$" + +new "netconf xmlns module ex2" +expecteof "$clixon_netconf -qf $cfg" 0 '99]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlns" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4299]]>]]>$" + +new "netconf discard-changes" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^]]>]]>$" + +new "netconf xmlns:ex" +expecteof "$clixon_netconf -qf $cfg" 0 '4422]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlns:ex" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^4422]]>]]>$" + +new "netconf xmlns:ex2" +expecteof "$clixon_netconf -qf $cfg" 0 '9999]]>]]>' '^]]>]]>$' + +new "netconf get config XXX xmlns:ex2" +expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "]]>]]>" "^44229999]]>]]>$" + +# rpc + +if [ $BE -ne 0 ]; then + exit # BE +fi +new "Kill backend" +# Check if premature kill +pid=`pgrep -u root -f clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -z -f $cfg +if [ $? -ne 0 ]; then + err "kill backend" +fi +sudo pkill -u root -f clixon_backend + +rm -rf $dir diff --git a/yang/clixon-config@2018-10-21.yang b/yang/clixon-config@2018-10-21.yang index 1f78d691..162c3c0b 100644 --- a/yang/clixon-config@2018-10-21.yang +++ b/yang/clixon-config@2018-10-21.yang @@ -123,7 +123,7 @@ module clixon-config { "Supported features as used by YANG feature/if-feature value is: :, where and are either names, or the special character '*'. - *:an* means enable all features + *:* means enable all features :* means enable all features in the specified module *: means enable the specific feature in all modules"; type string; @@ -150,7 +150,7 @@ module clixon-config { leaf CLICON_YANG_MAIN_DIR { type string; description - "If given, load all modules in this directory. + "If given, load all modules in this directory (all .yang files) See also CLICON_YANG_DIR which specifies a path of dirs"; } leaf CLICON_YANG_MODULE_MAIN { @@ -353,6 +353,15 @@ module clixon-config { Only works for Yang specified XML. If not set, all lists accessed via linear search."; } + leaf CLICON_XML_NS_ITERATE { + type boolean; + default true; + description + "If set, iterate through modules to find the matching datanode + or rpc if no xmlns attribute specifies namespace. + This is loose semantics of finding namespaces. + And it is wrong, but is the way Clixon originally was written."; + } leaf CLICON_USE_STARTUP_CONFIG { type int32; default 0; @@ -450,4 +459,12 @@ module clixon-config { } } + rpc debug { + description "Set debug level of backend."; + input { + leaf level { + type uint32; + } + } + } }