diff --git a/CHANGELOG.md b/CHANGELOG.md index b8274039..a995b13f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,27 @@ # Clixon CHANGELOG -## 3.3.1 +- Added new backend plugin callback: "plugin_statedata()" for retreiving state data + +- Added yang dir with ietf-netconf and clixon-config yang specs for internal usage. + +- Added state data: Netconf operation introduced; Error when + adding state data in . + +- Fixed bug where cli set of leaf-list were doubled, eg cli set foo -> foofoo + +- Restricted yang (sub)module file match to match RFC6020 exactly + +- Generalized yang type resolution to all included (sub)modules not just the topmost + +- Generic map_str2int generic mapping tables + +- Removed vector return values from xmldb_get() + +## 3.3.1 June 7 2017 - Fixed yang leafref cli completion. -- Removed non-standard api_path extension from internal netconf so that the internal com. +- Removed non-standard api_path extension from the internal netconf protocol so that the internal netcinf is now fully standard. - Strings in xmldb_put not properly encoded, eg eth/0 became eth.00000 diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 671ff219..b9548b49 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -219,10 +219,20 @@ from_client_get_config(clicon_handle h, clicon_err(OE_XML, 0, "db not found"); goto done; } + if (xmldb_validate_db(db) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", db); + goto ok; + } + if ((xfilter = xml_find(xe, "filter")) != NULL) if ((selector = xml_find_value(xfilter, "select"))==NULL) selector="/"; - if (xmldb_get(h, db, selector, &xret, NULL, NULL) < 0){ + if (xmldb_get(h, db, selector, 1, &xret) < 0){ cprintf(cbret, "" "operation-failed" "application" @@ -232,7 +242,6 @@ from_client_get_config(clicon_handle h, goto ok; } cprintf(cbret, ""); - /* if empty only , if any data then .. */ if (xret!=NULL){ if (xml_child_nr(xret)){ if (xml_name_set(xret, "config") < 0) @@ -250,6 +259,60 @@ from_client_get_config(clicon_handle h, return retval; } +/*! Internal message: get + * + * @param[in] h Clicon handle + * @param[in] xe Netconf request xml tree + * @param[out] cbret Return xml value cligen buffer + * @see from_client_get_config + */ +static int +from_client_get(clicon_handle h, + cxobj *xe, + cbuf *cbret) +{ + int retval = -1; + cxobj *xfilter; + char *selector = "/"; + cxobj *xret = NULL; + + if ((xfilter = xml_find(xe, "filter")) != NULL) + if ((selector = xml_find_value(xfilter, "select"))==NULL) + selector="/"; + /* Get config */ + if (xmldb_get(h, "running", selector, 0, &xret) < 0){ + cprintf(cbret, "" + "operation-failed" + "application" + "error" + "read-registry" + ""); + goto ok; + } + /* Get state data from plugins as defined by plugin_statedata(), if any */ + assert(xret); + if (backend_statedata_call(h, selector, xret) < 0) + goto done; + cprintf(cbret, ""); + /* if empty only */ + if (xret!=NULL){ + if (xml_child_nr(xret)){ + if (xml_name_set(xret, "config") < 0) + goto done; + if (clicon_xml2cbuf(cbret, xret, 0, 0) < 0) + goto done; + } + } + cprintf(cbret, ""); + ok: + retval = 0; + done: + if (xret) + xml_free(xret); + return retval; +} + + /*! Internal message: edit-config * * @param[in] h Clicon handle @@ -271,11 +334,26 @@ from_client_edit_config(clicon_handle h, cxobj *x; enum operation_type operation = OP_MERGE; int piddb; + int non_config = 0; + yang_spec *yspec; + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } if ((target = netconf_db_find(xn, "target")) == NULL){ clicon_err(OE_XML, 0, "db not found"); goto done; } + if (xmldb_validate_db(target) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", target); + goto ok; + } /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ @@ -300,6 +378,20 @@ from_client_edit_config(clicon_handle h, } } if ((xc = xpath_first(xn, "config")) != NULL){ + 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) + goto done; + if (non_config){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "state data not allowed" + ""); + goto ok; + } + if (xmldb_put(h, target, operation, xc) < 0){ cprintf(cbret, "" "operation-failed" @@ -356,6 +448,16 @@ from_client_lock(clicon_handle h, ""); goto ok; } + if (xmldb_validate_db(db) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", db); + goto ok; + } + /* * A lock MUST not be granted if either of the following conditions is true: * 1) A lock is already held by any NETCONF session or another entity. @@ -410,6 +512,15 @@ from_client_unlock(clicon_handle h, ""); goto ok; } + if (xmldb_validate_db(db) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", db); + goto ok; + } piddb = xmldb_islocked(h, db); /* * An unlock operation will not succeed if any of the following @@ -534,6 +645,16 @@ from_client_copy_config(clicon_handle h, ""); goto ok; } + if (xmldb_validate_db(source) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", source); + goto ok; + } + if ((target = netconf_db_find(xe, "target")) == NULL){ cprintf(cbret, "" "missing-element" @@ -543,6 +664,15 @@ from_client_copy_config(clicon_handle h, ""); goto ok; } + if (xmldb_validate_db(target) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", target); + goto ok; + } /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ @@ -556,7 +686,6 @@ from_client_copy_config(clicon_handle h, piddb); goto ok; } - if (xmldb_copy(h, source, target) < 0){ cprintf(cbret, "" "operation-failed" @@ -601,6 +730,16 @@ from_client_delete_config(clicon_handle h, ""); goto ok; } + if (xmldb_validate_db(target) < 0){ + cprintf(cbret, "" + "invalid-value" + "protocol" + "error" + "No such database: %s" + "", target); + goto ok; + } + /* Check if target locked by other client */ piddb = xmldb_islocked(h, target); if (piddb && mypid != piddb){ @@ -808,6 +947,10 @@ from_client_msg(clicon_handle h, if (from_client_unlock(h, xe, pid, cbret) < 0) goto done; } + else if (strcmp(name, "get") == 0){ + if (from_client_get(h, xe, cbret) < 0) + goto done; + } else if (strcmp(name, "close-session") == 0){ xmldb_unlock_all(h, pid); cprintf(cbret, ""); @@ -846,7 +989,7 @@ from_client_msg(clicon_handle h, goto done; } else{ - if ((ret = backend_netconf_plugin_callbacks(h, xe, ce, cbret)) < 0) + if ((ret = backend_rpc_cb_call(h, xe, ce, cbret)) < 0) goto done; if (ret == 0) /* not handled by callback */ cprintf(cbret, "" diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 95b15869..ec0b0887 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -146,9 +146,9 @@ validate_common(clicon_handle h, goto done; } /* 2. Parse xml trees */ - if (xmldb_get(h, "running", "/", &td->td_src, NULL, NULL) < 0) + if (xmldb_get(h, "running", "/", 1, &td->td_src) < 0) goto done; - if (xmldb_get(h, candidate, "/", &td->td_target, NULL, NULL) < 0) + if (xmldb_get(h, candidate, "/", 1, &td->td_target) < 0) goto done; /* 3. Compute differences */ @@ -212,7 +212,8 @@ validate_common(clicon_handle h, * The code reverts changes if the commit fails. But if the revert * fails, we just ignore the errors and proceed. Maybe we should * do something more drastic? - * @param[in] h Clicon handle + * @param[in] h Clicon handle + * @param[in] candidate A candidate database, not necessarily "candidate" */ int candidate_commit(clicon_handle h, @@ -283,17 +284,17 @@ from_client_commit(clicon_handle h, piddb); goto ok; } - if (candidate_commit(h, "candidate") < 0){ clicon_debug(1, "Commit candidate failed"); - /* XXX: candidate_validate should have proper error handling */ cprintf(cbret, "" - "missing-attribute" + "invalid-value" "protocol" "error" "%s" "", clicon_err_reason); + goto ok; + goto ok; } cprintf(cbret, ""); diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 52f11bdc..ec560db4 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -87,6 +87,8 @@ backend_terminate(clicon_handle h) if ((yspec = clicon_dbspec_yang(h)) != NULL) yspec_free(yspec); plugin_finish(h); + /* Delete all backend plugin RPC callbacks */ + backend_rpc_cb_delete_all(); if (pidfile) unlink(pidfile); if (sockpath) @@ -212,7 +214,7 @@ done: static int candb_reset(clicon_handle h) { - int retval = -1; + int retval = -1; if (xmldb_copy(h, "running", "tmp") < 0){ clicon_err(OE_UNIX, errno, "file copy"); @@ -590,7 +592,7 @@ main(int argc, char **argv) *(argv-1) = tmp; if (reload_running){ - /* This could be afailed validation, and we should not fail for that */ + /* This could be a failed validation, and we should not fail for that */ (void)candidate_commit(h, "candidate"); } diff --git a/apps/backend/backend_plugin.c b/apps/backend/backend_plugin.c index 1a74fb7a..9bb6b131 100644 --- a/apps/backend/backend_plugin.c +++ b/apps/backend/backend_plugin.c @@ -68,11 +68,22 @@ * Types */ /* Following are specific to backend. For common see clicon_plugin.h - * @note the following should match the prototypes in clicon_backend.h + * @note the following should match the prototypes in clixon_backend.h */ #define PLUGIN_RESET "plugin_reset" typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status */ +/*! Plugin callback, if defined called to get state data from plugin + * @param[in] h Clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in] xtop XML tree, on entry. + * @retval 0 OK + * @retval -1 Error + * @see xmldb_get + */ +#define PLUGIN_STATEDATA "plugin_statedata" +typedef int (plgstatedata_t)(clicon_handle h, char *xpath, cxobj *xtop); + #define PLUGIN_TRANS_BEGIN "transaction_begin" #define PLUGIN_TRANS_VALIDATE "transaction_validate" #define PLUGIN_TRANS_COMPLETE "transaction_complete" @@ -80,6 +91,7 @@ typedef int (plgreset_t)(clicon_handle h, char *dbname); /* Reset system status #define PLUGIN_TRANS_END "transaction_end" #define PLUGIN_TRANS_ABORT "transaction_abort" + typedef int (trans_cb_t)(clicon_handle h, transaction_data td); /* Transaction cbs */ /* Backend (config) plugins */ @@ -90,12 +102,14 @@ struct plugin { plgstart_t *p_start; /* Start */ plgexit_t *p_exit; /* Exit */ plgreset_t *p_reset; /* Reset state */ + plgstatedata_t *p_statedata; /* State-data callback */ trans_cb_t *p_trans_begin; /* Transaction start */ trans_cb_t *p_trans_validate; /* Transaction validation */ trans_cb_t *p_trans_complete; /* Transaction validation complete */ trans_cb_t *p_trans_commit; /* Transaction commit */ trans_cb_t *p_trans_end; /* Transaction completed */ trans_cb_t *p_trans_abort; /* Transaction aborted */ + }; /* @@ -212,6 +226,8 @@ backend_plugin_load (clicon_handle h, clicon_debug(2, "%s callback registered.", PLUGIN_EXIT); if ((new->p_reset = dlsym(handle, PLUGIN_RESET)) != NULL) clicon_debug(2, "%s callback registered.", PLUGIN_RESET); + if ((new->p_statedata = dlsym(handle, PLUGIN_STATEDATA)) != NULL) + clicon_debug(2, "%s callback registered.", PLUGIN_STATEDATA); if ((new->p_trans_begin = dlsym(handle, PLUGIN_TRANS_BEGIN)) != NULL) clicon_debug(2, "%s callback registered.", PLUGIN_TRANS_BEGIN); if ((new->p_trans_validate = dlsym(handle, PLUGIN_TRANS_VALIDATE)) != NULL) @@ -700,3 +716,82 @@ plugin_transaction_abort(clicon_handle h, return retval; } +/*---------------------------------------------------------------------- + * Backend state data callbacks + */ + +/*! Go through all backend statedata callbacks and collect state data + * This is internal system call, plugin is invoked (does not call) this function + * Backend plugins can register + * @param[in] h clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in,out] xml XML tree. + * @retval -1 Error + * @retval 0 OK + */ +int +backend_statedata_call(clicon_handle h, + char *xpath, + cxobj *xtop) +{ + int retval = -1; + struct plugin *p; + int i; + cxobj *x = NULL; + yang_spec *yspec; + cxobj **xvec = NULL; + size_t xlen; + + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + if (xtop==NULL){ + clicon_err(OE_CFG, ENOENT, "XML tree expected"); + goto done; + } + for (i = 0; i < nplugins; i++) { + p = &plugins[i]; + if (p->p_statedata) { + if ((x = xml_new("config", NULL)) == NULL) + goto done; + if ((p->p_statedata)(h, xpath, x) < 0) + goto done; + if (xml_merge(xtop, x, yspec) < 0) + goto done; + if (x){ + xml_free(x); + x = NULL; + } + } + } + { + /* Code complex to filter out anything that is outside of xpath */ + if (xpath_vec(xtop, xpath?xpath:"/", &xvec, &xlen) < 0) + goto done; + + /* If vectors are specified then mark the nodes found and + * then filter out everything else, + * otherwise return complete tree. + */ + if (xvec != NULL){ + for (i=0; inr_callback = cb; - nr->nr_arg = arg; - nr->nr_tag = strdup(tag); /* XXX strdup memleak */ - INSQ(nr, deps); + memset (rc, 0, sizeof (*rc)); + rc->rc_callback = cb; + rc->rc_arg = arg; + rc->rc_tag = strdup(tag); /* XXX strdup memleak */ + INSQ(rc, rpc_cb_list); return 0; catch: - if (nr){ - if (nr->nr_tag) - free(nr->nr_tag); - free(nr); + if (rc){ + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); } return -1; } -/*! See if there is any callback registered for this tag - * +/*! Search netconf backend callbacks and invoke if match + * This is internal system call, plugin is invoked (does not call) this functino * @param[in] h clicon handle * @param[in] xe Sub-tree (under xorig) at child of rpc: . * @param[in] ce Client (session) entry @@ -480,28 +489,49 @@ catch: * @retval -1 Error * @retval 0 OK, not found handler. * @retval 1 OK, handler called + * @see backend_rpc_cb_register */ int -backend_netconf_plugin_callbacks(clicon_handle h, - cxobj *xe, - struct client_entry *ce, - cbuf *cbret) +backend_rpc_cb_call(clicon_handle h, + cxobj *xe, + struct client_entry *ce, + cbuf *cbret) { - backend_netconf_reg_t *nreg; - int retval; + backend_rpc_cb_entry *rc; + int retval = -1; - if (deps == NULL) + if (rpc_cb_list == NULL) return 0; - nreg = deps; + rc = rpc_cb_list; do { - if (strcmp(nreg->nr_tag, xml_name(xe)) == 0){ - if ((retval = nreg->nr_callback(h, xe, ce, cbret, nreg->nr_arg)) < 0) - return -1; - else - return 1; /* handled */ + if (strcmp(rc->rc_tag, xml_name(xe)) == 0){ + if ((retval = rc->rc_callback(h, xe, ce, cbret, rc->rc_arg)) < 0) + goto done; + else{ + retval = 1; /* handled */ + goto done; + } } - nreg = NEXTQ(backend_netconf_reg_t *, nreg); - } while (nreg != deps); + rc = NEXTQ(backend_rpc_cb_entry *, rc); + } while (rc != rpc_cb_list); + retval = 0; + done: + return retval; +} + +/*! Delete all state data callbacks. + */ +int +backend_rpc_cb_delete_all(void) +{ + backend_rpc_cb_entry *rc; + + while((rc = rpc_cb_list) != NULL) { + DELQ(rc, rpc_cb_list, backend_rpc_cb_entry *); + if (rc->rc_tag) + free(rc->rc_tag); + free(rc); + } return 0; } diff --git a/apps/backend/clixon_backend_handle.h b/apps/backend/clixon_backend_handle.h index b309f2a2..0bca3835 100644 --- a/apps/backend/clixon_backend_handle.h +++ b/apps/backend/clixon_backend_handle.h @@ -44,13 +44,15 @@ * Types */ struct client_entry; -typedef int (*backend_netconf_cb_t)( +typedef int (*backend_rpc_cb)( clicon_handle h, - cxobj *xe, /* Request: */ - struct client_entry *ce, /* Client session */ - cbuf *cbret, /* Reply eg ... */ - void *arg /* Argument given at register */ + cxobj *xe, /* Request: */ + struct client_entry *ce, /* Client session */ + cbuf *cbret,/* Reply eg ... */ + void *arg /* Argument given at register */ ); +typedef backend_rpc_cb backend_netconf_cb_t; /* XXX backward compat */ + /*! Generic downcall registration. * Enables any function to be called from (cli) frontend @@ -88,14 +90,16 @@ int subscription_delete(clicon_handle h, char *stream, subscription_fn_t fn, void *arg); struct handle_subscription *subscription_each(clicon_handle h, - struct handle_subscription *hprev); + struct handle_subscription *hprev); -int backend_netconf_register_callback(clicon_handle h, - backend_netconf_cb_t cb, /* Callback called */ - void *arg, /* Arg to send to callback */ - char *tag); /* Xml tag when callback is made */ +/* XXX backward compat */ +#define backend_netconf_register_callback(a,b,c,d) backend_rpc_cb_register(a,b,c,d) +int backend_rpc_cb_register(clicon_handle h, backend_rpc_cb cb, void *arg, + char *tag); -int backend_netconf_plugin_callbacks(clicon_handle h, cxobj *xe, - struct client_entry *ce, cbuf *cbret); +int backend_rpc_cb_call(clicon_handle h, cxobj *xe, struct client_entry *ce, + cbuf *cbret); + +int backend_rpc_cb_delete_all(void); #endif /* _CLIXON_BACKEND_HANDLE_H_ */ diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 71f58047..a507bd9c 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -243,7 +243,7 @@ cli_dbxml(clicon_handle h, xml_type_set(xa, CX_ATTR); if (xml_value_set(xa, xml_operation2str(op)) < 0) goto done; - if (y->yn_keyword != Y_LIST){ + if (y->yn_keyword != Y_LIST && y->yn_keyword != Y_LEAF_LIST){ len = cvec_len(cvv); if (len > 1){ cval = cvec_i(cvv, len-1); @@ -644,6 +644,7 @@ compare_dbs(clicon_handle h, { cxobj *xc1 = NULL; /* running xml */ cxobj *xc2 = NULL; /* candidate xml */ + cxobj *xerr; int retval = -1; int astext; @@ -657,8 +658,16 @@ compare_dbs(clicon_handle h, astext = 0; if (clicon_rpc_get_config(h, "running", "/", &xc1) < 0) goto done; + if ((xerr = xpath_first(xc1, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } if (clicon_rpc_get_config(h, "candidate", "/", &xc2) < 0) goto done; + if ((xerr = xpath_first(xc2, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } if (compare_xmls(xc1, xc2, astext) < 0) /* astext? */ goto done; retval = 0; @@ -800,6 +809,7 @@ save_config_file(clicon_handle h, char *dbstr; char *varstr; cxobj *xt = NULL; + cxobj *xerr; FILE *f = NULL; if (cvec_len(argv) != 2){ @@ -825,6 +835,10 @@ save_config_file(clicon_handle h, filename = cv_string_get(cv); if (clicon_rpc_get_config(h, dbstr,"/", &xt) < 0) goto done; + if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } if ((f = fopen(filename, "wb")) == NULL){ clicon_err(OE_CFG, errno, "Creating file %s", filename); goto done; @@ -1122,6 +1136,7 @@ cli_copy_config(clicon_handle h, char *tovar; cg_var *tocv; char *toname; + cxobj *xerr; if (cvec_len(argv) != 5){ clicon_err(OE_PLUGIN, 0, "%s: Requires four elements: ", __FUNCTION__); @@ -1164,6 +1179,10 @@ cli_copy_config(clicon_handle h, /* Get from object configuration and store in x1 */ if (clicon_rpc_get_config(h, db, cbuf_get(cb), &x1) < 0) goto done; + if ((xerr = xpath_first(x1, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } /* Get to variable -> cv -> to name */ if ((tocv = cvec_find_var(cvv, tovar)) == NULL){ diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index dcdb1730..394c5d03 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -657,7 +657,7 @@ clicon_parse(clicon_handle h, } res = cliread_parse(cli_cligen(h), cmd, pt, &match_obj, cvv); if (res != CG_MATCH) - pt_expand_cleanup_1(pt); + pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */ if (msav){ cli_tree_active_set(h, msav); free(msav); @@ -689,7 +689,7 @@ clicon_parse(clicon_handle h, } if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0) cli_handler_err(stdout); - pt_expand_cleanup_1(pt); + pt_expand_cleanup_1(pt); /* XXX change to pt_expand_treeref_cleanup */ if (result) *result = r; goto done; diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index f86fc9da..6240ac31 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -104,6 +104,7 @@ expand_dbvar(void *h, cxobj *xt = NULL; char *xpath = NULL; cxobj **xvec = NULL; + cxobj *xerr; size_t xlen = 0; cxobj *x; char *bodystr; @@ -142,6 +143,10 @@ expand_dbvar(void *h, /* XXX read whole configuration, why not send xpath? */ if (clicon_rpc_get_config(h, dbstr, "/", &xt) < 0) goto done; + if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } /* One round to detect duplicates * XXX The code below would benefit from some cleanup */ @@ -379,6 +384,7 @@ cli_show_config(clicon_handle h, char *val = NULL; cxobj *xt = NULL; cxobj *xc; + cxobj *xerr; enum genmodel_type gt; if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ @@ -428,6 +434,10 @@ cli_show_config(clicon_handle h, /* Get configuration from database */ if (clicon_rpc_get_config(h, db, cbuf_get(cbxpath), &xt) < 0) goto done; + if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } /* Print configuration according to format */ switch (format){ case FORMAT_XML: @@ -487,6 +497,7 @@ show_conf_xpath(clicon_handle h, char *xpath; cg_var *cv; cxobj *xt = NULL; + cxobj *xerr; cxobj **xv = NULL; size_t xlen; int i; @@ -507,6 +518,10 @@ show_conf_xpath(clicon_handle h, xpath = cv_string_get(cv); if (clicon_rpc_get_config(h, str, xpath, &xt) < 0) goto done; + if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + clicon_rpc_generate_error(xerr); + goto done; + } if (xpath_vec(xt, xpath, &xv, &xlen) < 0) goto done; for (i=0; i... level. + * @param[out] xret Return XML, error or OK + * @note filter type subtree and xpath is supported, but xpath is preferred, and + * better performance and tested. Please use xpath. + * + * @example + * + * ]]>]]> + */ +static int +netconf_get(clicon_handle h, + cxobj *xn, + cxobj **xret) +{ + cxobj *xfilter; /* filter */ + int retval = -1; + char *ftype = NULL; + cxobj *xfilterconf; + cxobj *xconf; + + /* ie ... */ + if ((xfilter = xpath_first(xn, "filter")) != NULL) + ftype = xml_find_value(xfilter, "type"); + if (ftype == NULL || strcmp(ftype, "xpath")==0){ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + } + else if (strcmp(ftype, "subtree")==0){ + /* Default rfc filter is subtree. I prefer xpath and use it internally. + Get whole subtree and then filter aftwerwards. This is suboptimal. + Therefore please use xpath. + */ + if (clicon_rpc_netconf_xml(h, xml_parent(xn), xret, NULL) < 0) + goto done; + if (xfilter && + (xfilterconf = xpath_first(xfilter, "//configuration"))!= NULL && + (xconf = xpath_first(*xret, "/rpc-reply/data/configuration")) != NULL){ + /* xml_filter removes parts of xml tree not matching */ + if ((strcmp(xml_name(xfilterconf), xml_name(xconf))!=0) || + xml_filter(xfilterconf, xconf) < 0){ + clicon_xml_parse(xret, "" + "operation-failed" + "applicatio" + "error" + "filtering" + ""); + } + } + } + else{ + clicon_xml_parse(xret, "" + "operation-failed" + "applicatio" + "error" + "filter type not supported" + "type" + ""); + } + // ok: /* netconf error is not fatal */ + retval = 0; + done: + return retval; +} + + /*! Close a (user) session * @param[in] xn Sub-tree (under xorig) at ... level. @@ -818,6 +888,8 @@ netconf_rpc_dispatch(clicon_handle h, return netconf_lock(h, xe, xret); else if (strcmp(xml_name(xe), "unlock") == 0) return netconf_unlock(h, xe, xret); + else if (strcmp(xml_name(xe), "get") == 0) + return netconf_get(h, xe, xret); else if (strcmp(xml_name(xe), "close-session") == 0) return netconf_close_session(h, xe, xret); else if (strcmp(xml_name(xe), "kill-session") == 0) diff --git a/apps/restconf/README.md b/apps/restconf/README.md index 9dfb15d4..ab941091 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -60,7 +60,7 @@ olof@vandal> curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=et } ] -curl -sX POST -d '{"clicon":{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}}' http://localhost/restconf/data +curl -sX POST -d '{"interfaces":{"interface":{"name":"eth1","type":"eth","enabled":"true"}}}' http://localhost/restconf/data ``` ### Debugging diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index dd1d6d20..1f0073f7 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -57,6 +57,96 @@ #include "restconf_lib.h" +/* See RFC 8040 Section 7: Mapping from NETCONF to Status Code + * and RFC 6241 Appendix A. NETCONF Error list + */ +static const map_str2int netconf_restconf_map[] = { + {"in-use", 409}, + {"invalid-value", 400}, + {"invalid-value", 404}, + {"invalid-value", 406}, + {"too-big", 413}, /* request */ + {"too-big", 400}, /* response */ + {"missing-attribute", 400}, + {"bad-attribute", 400}, + {"unknown-attribute", 400}, + {"bad-element", 400}, + {"unknown-element", 400}, + {"unknown-namespace", 400}, + {"access-denied", 401}, + {"access-denied", 403}, + {"lock-denied", 409}, + {"resource-denied", 409}, + {"rollback-failed", 500}, + {"data-exists", 409}, + {"data-missing", 409}, + {"operation-not-supported",405}, + {"operation-not-supported",501}, + {"operation-failed", 412}, + {"operation-failed", 500}, + {"partial-operation", 500}, + {"malformed-message", 400}, + {NULL, -1} +}; + +/* See 7231 Section 6.1 + */ +static const map_str2int http_reason_phrase_map[] = { + {"Continue", 100}, + {"Switching Protocols", 101}, + {"OK", 200}, + {"Created", 201}, + {"Accepted", 202}, + {"Non-Authoritative Information", 203}, + {"No Content", 204}, + {"Reset Content", 205}, + {"Partial Content", 206}, + {"Multiple Choices", 300}, + {"Moved Permanently", 301}, + {"Found", 302}, + {"See Other", 303}, + {"Not Modified", 304}, + {"Use Proxy", 305}, + {"Temporary Redirect", 307}, + {"Bad Request", 400}, + {"Unauthorized", 401}, + {"Payment Required", 402}, + {"Forbidden", 403}, + {"Not Found", 404}, + {"Method Not Allowed", 405}, + {"Not Acceptable", 406}, + {"Proxy Authentication Required", 407}, + {"Request Timeout", 408}, + {"Conflict", 409}, + {"Gone", 410}, + {"Length Required", 411}, + {"Precondition Failed", 412}, + {"Payload Too Large", 413}, + {"URI Too Long", 414}, + {"Unsupported Media Type", 415}, + {"Range Not Satisfiable", 416}, + {"Expectation Failed", 417}, + {"Upgrade Required", 426}, + {"Internal Server Error", 500}, + {"Not Implemented", 501}, + {"Bad Gateway", 502}, + {"Service Unavailable", 503}, + {"Gateway Timeout", 504}, + {"HTTP Version Not Supported", 505}, + {NULL, -1} +}; + +int +restconf_err2code(char *tag) +{ + return clicon_str2int(netconf_restconf_map, tag); +} + +const char * +restconf_code2reason(int code) +{ + return clicon_int2str(http_reason_phrase_map, code); +} /*! */ @@ -90,6 +180,16 @@ badrequest(FCGX_Request *r) return 0; } +int +notimplemented(FCGX_Request *r) +{ + clicon_debug(1, "%s", __FUNCTION__); + FCGX_FPrintF(r->out, "Status: 501\r\n"); + FCGX_FPrintF(r->out, "Content-Type: text/html\r\n\r\n"); + FCGX_FPrintF(r->out, "

Not Implemented/h1>\n"); + return 0; +} + int conflict(FCGX_Request *r) { diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index b0cdf7c9..1ebcb74a 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -43,8 +43,11 @@ /* * Prototypes */ +int restconf_err2code(char *tag); +const char *restconf_code2reason(int code); int notfound(FCGX_Request *r); int badrequest(FCGX_Request *r); +int notimplemented(FCGX_Request *r); int conflict(FCGX_Request *r); int clicon_debug_xml(int dbglevel, char *str, cxobj *cx); int test(FCGX_Request *r, int dbg); diff --git a/apps/restconf/restconf_methods.c b/apps/restconf/restconf_methods.c index f24cce22..9e2f4db5 100644 --- a/apps/restconf/restconf_methods.c +++ b/apps/restconf/restconf_methods.c @@ -156,6 +156,11 @@ api_data_get_gen(clicon_handle h, cxobj **vec = NULL; yang_spec *yspec; cxobj *xret = NULL; + cxobj *xerr; + cxobj *xtag; + cbuf *cbj = NULL;; + int code; + const char *reason_phrase; clicon_debug(1, "%s", __FUNCTION__); yspec = clicon_dbspec_yang(h); @@ -166,16 +171,45 @@ api_data_get_gen(clicon_handle h, goto done; } clicon_debug(1, "%s path:%s", __FUNCTION__, cbuf_get(path)); - if (clicon_rpc_get_config(h, "running", cbuf_get(path), &xret) < 0){ + if (clicon_rpc_get(h, cbuf_get(path), &xret) < 0){ notfound(r); goto done; } +#if 0 /* DEBUG */ { cbuf *cb = cbuf_new(); - clicon_xml2cbuf(cb, xret, 0, 0); + xml2json_cbuf(cb, xret, 1); clicon_debug(1, "%s xret:%s", __FUNCTION__, cbuf_get(cb)); cbuf_free(cb); } +#endif + if ((xerr = xpath_first(xret, "/rpc-error")) != NULL){ + if ((cbj = cbuf_new()) == NULL) + goto done; + if ((xtag = xpath_first(xerr, "/error-tag")) == NULL){ + notfound(r); /* bad reply? */ + goto done; + } + code = restconf_err2code(xml_body(xtag)); + if ((reason_phrase = restconf_code2reason(code)) == NULL) + reason_phrase=""; + clicon_debug(1, "%s code:%d reason phrase:%s", + __FUNCTION__, code, reason_phrase); + + if (xml_name_set(xerr, "error") < 0) + goto done; + if (xml2json_cbuf(cbj, xerr, 1) < 0) + goto done; + FCGX_FPrintF(r->out, "Status: %d %s\r\n", code, reason_phrase); + FCGX_FPrintF(r->out, "Content-Type: application/yang-data+json\r\n\r\n"); + FCGX_FPrintF(r->out, "\r\n"); + FCGX_FPrintF(r->out, "{\r\n"); + FCGX_FPrintF(r->out, " \"ietf-restconf:errors\" : {\r\n"); + FCGX_FPrintF(r->out, " %s", cbuf_get(cbj)); + FCGX_FPrintF(r->out, " }\r\n"); + FCGX_FPrintF(r->out, "}\r\n"); + goto ok; + } if ((cbx = cbuf_new()) == NULL) goto done; FCGX_SetExitStatus(200, r->out); /* OK */ @@ -197,6 +231,8 @@ api_data_get_gen(clicon_handle h, clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); if (cbx) cbuf_free(cbx); + if (cbj) + cbuf_free(cbj); if (path) cbuf_free(path); if (xret) @@ -505,8 +541,7 @@ api_data_patch(clicon_handle h, cvec *qvec, char *data) { - badrequest(r); - // return api_data_edit(h, r, api_path, pcvec, pi, qvec, data, OP_MERGE); + notimplemented(r); return 0; } @@ -555,6 +590,7 @@ api_data_delete(clicon_handle h, goto done; if ((cbx = cbuf_new()) == NULL) goto done; + if (clicon_xml2cbuf(cbx, xtop, 0, 0) < 0) goto done; if (clicon_rpc_edit_config(h, "candidate", diff --git a/clixon.conf.cpp.cpp b/clixon.conf.cpp.cpp index 1a6f441d..f0a5afb0 100644 --- a/clixon.conf.cpp.cpp +++ b/clixon.conf.cpp.cpp @@ -95,8 +95,6 @@ CLICON_BACKEND_PIDFILE localstatedir/APPNAME/APPNAME.pidfile # Set if all configuration changes are committed directly, commit command unnecessary # CLICON_AUTOCOMMIT 0 -# CLICON_COMMIT_ORDER 0 - # Name of master plugin (both frontend and backend). Master plugin has special # callbacks for frontends. See clicon user manual for more info. # CLICON_MASTER_PLUGIN master diff --git a/configure b/configure index 6d017b08..7670870b 100755 --- a/configure +++ b/configure @@ -2136,7 +2136,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MINOR="3" -CLIXON_VERSION_PATCH="1" +CLIXON_VERSION_PATCH="2" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" @@ -2172,8 +2172,8 @@ _ACEOF # Bind to specific CLIgen version -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}" >&5 -$as_echo "CLIXON version is ${CLIXON_VERSION}" >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CLIXON version is ${CLIXON_VERSION}_PRE" >&5 +$as_echo "CLIXON version is ${CLIXON_VERSION}_PRE" >&6; } ac_aux_dir= for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do diff --git a/configure.ac b/configure.ac index 1fd02985..78ec5525 100644 --- a/configure.ac +++ b/configure.ac @@ -43,7 +43,7 @@ AC_INIT(lib/clixon/clixon.h.in) CLIXON_VERSION_MAJOR="3" CLIXON_VERSION_MINOR="3" -CLIXON_VERSION_PATCH="1" +CLIXON_VERSION_PATCH="2" CLIXON_VERSION="\"${CLIXON_VERSION_MAJOR}.${CLIXON_VERSION_MINOR}.${CLIXON_VERSION_PATCH}\"" # Fix to specific version (eg 3.5) or head (3) CLIGEN_VERSION="3" @@ -62,7 +62,7 @@ AC_SUBST(CLIXON_VERSION_MAJOR) AC_SUBST(CLIXON_VERSION_MINOR) AC_SUBST(CLIGEN_VERSION) # Bind to specific CLIgen version -AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION}) +AC_MSG_RESULT(CLIXON version is ${CLIXON_VERSION}_PRE) AC_CANONICAL_TARGET AC_SUBST(CC) diff --git a/datastore/datastore_client.c b/datastore/datastore_client.c index d2a023f8..82d49e69 100644 --- a/datastore/datastore_client.c +++ b/datastore/datastore_client.c @@ -213,7 +213,7 @@ main(int argc, char **argv) if (strcmp(cmd, "get")==0){ if (argc != 1 && argc != 2) usage(argv0); - if (xmldb_get(h, db, argc==2?argv[1]:"/", &xt, NULL, 0) < 0) + if (xmldb_get(h, db, argc==2?argv[1]:"/", 0, &xt) < 0) goto done; clicon_xml2file(stdout, xt, 0, 0); diff --git a/datastore/keyvalue/clixon_keyvalue.c b/datastore/keyvalue/clixon_keyvalue.c index d546aac1..56b7196b 100644 --- a/datastore/keyvalue/clixon_keyvalue.c +++ b/datastore/keyvalue/clixon_keyvalue.c @@ -71,17 +71,6 @@ * cli_expand_var_generate | yang2api_path_fmt | * yang -------------> | | * +----------------+ - * xmldb_get_tree - * - compare_dbs - * - netconf - * - validate - * - from_client_save - * - * xmldb_get_vec - * - restconf - * - expand_dbvar - * - show_conf_xpath - * * dependency on clixon handle: * clixon_xmldb_dir() * clicon_dbspec_yang(h) @@ -571,38 +560,15 @@ kv_setopt(xmldb_handle xh, /*! Get content of database using xpath. return a set of matching sub-trees * The function returns a minimal tree that includes all sub-trees that match * xpath. - * @param[in] dbname Name of database to search in (filename including dir path - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[out] xtop Single XML tree which xvec points to. Free with xml_free() - * @param[out] xvec Vector of xml trees. Free after use. - * @param[out] xlen Length of vector. - * @retval 0 OK - * @retval -1 Error - * @code - * cxobj *xt; - * cxobj **xvec; - * size_t xlen; - * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", - * &xt, &xvec, &xlen) < 0) - * err; - * for (i=0; i17", &xt) < 0) - * err; - * if (xmldb_put(h, "running", OP_MERGE, NULL, xt) < 0) - * err; - * @endcode - * @see xmldb_put_xkey for single key + * This is a clixon datastore plugin of the the xmldb api + * @see xmldb_put */ int kv_put(xmldb_handle xh, char *db, enum operation_type op, - cxobj *xt) + cxobj *xt) { int retval = -1; struct kv_handle *kh = handle(xh); diff --git a/datastore/keyvalue/clixon_keyvalue.h b/datastore/keyvalue/clixon_keyvalue.h index a2a77e6c..83654df0 100644 --- a/datastore/keyvalue/clixon_keyvalue.h +++ b/datastore/keyvalue/clixon_keyvalue.h @@ -39,8 +39,7 @@ /* * Prototypes */ -int kv_get(xmldb_handle h, char *db, char *xpath, - cxobj **xtop, cxobj ***xvec, size_t *xlen); +int kv_get(xmldb_handle h, char *db, char *xpath, int config, cxobj **xtop); int kv_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt); int kv_dump(FILE *f, char *dbfilename, char *rxkey); int kv_copy(xmldb_handle h, char *from, char *to); diff --git a/datastore/text/Makefile.in b/datastore/text/Makefile.in index 79c97729..5cc95cb2 100644 --- a/datastore/text/Makefile.in +++ b/datastore/text/Makefile.in @@ -85,7 +85,7 @@ install: $(PLUGIN) install-include: uninstall: - rm -rf $(DESTDIR)$(clixon_LIBDIR)/xmldb/$(PLUGIN) + rm -rf $(DESTDIR)$(libdir)/xmldb/$(PLUGIN) TAGS: find . -name '*.[chyl]' -print | etags - diff --git a/datastore/text/clixon_xmldb_text.c b/datastore/text/clixon_xmldb_text.c index 742ea639..a6357508 100644 --- a/datastore/text/clixon_xmldb_text.c +++ b/datastore/text/clixon_xmldb_text.c @@ -68,9 +68,10 @@ /*! Internal structure of text datastore handle. */ struct text_handle { - int th_magic; /* magic */ - char *th_dbdir; /* Directory of database files */ - yang_spec *th_yangspec; /* Yang spec if this datastore */ + int th_magic; /* magic */ + char *th_dbdir; /* Directory of database files */ + yang_spec *th_yangspec; /* Yang spec if this datastore */ + clicon_hash_t *th_dbs; /* Hash of databases */ }; /*! Check struct magic number for sanity checks @@ -85,15 +86,6 @@ text_handle_check(xmldb_handle xh) return th->th_magic == TEXT_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. - * @note This should probably be on file-system - */ -static int _running_locked = 0; -static int _candidate_locked = 0; -static int _startup_locked = 0; - /*! Translate from symbolic database name to actual filename in file-system * @param[in] th text handle handle * @param[in] db Symbolic database name, eg "candidate", "running" @@ -107,8 +99,8 @@ static int _startup_locked = 0; */ static int text_db2file(struct text_handle *th, - char *db, - char **filename) + char *db, + char **filename) { int retval = -1; cbuf *cb; @@ -122,13 +114,6 @@ text_db2file(struct text_handle *th, clicon_err(OE_XML, errno, "dbdir not set"); goto done; } - if (strcmp(db, "running") != 0 && - strcmp(db, "candidate") != 0 && - strcmp(db, "startup") != 0 && - strcmp(db, "tmp") != 0){ - clicon_err(OE_XML, 0, "No such database: %s", db); - goto done; - } cprintf(cb, "%s/%s_db", dir, db); if ((*filename = strdup4(cbuf_get(cb))) == NULL){ clicon_err(OE_UNIX, errno, "strdup"); @@ -159,6 +144,8 @@ text_connect(void) } memset(th, 0, size); th->th_magic = TEXT_HANDLE_MAGIC; + if ((th->th_dbs = hash_init()) == NULL) + goto done; xh = (xmldb_handle)th; done: return xh; @@ -178,6 +165,8 @@ text_disconnect(xmldb_handle xh) if (th){ if (th->th_dbdir) free(th->th_dbdir); + if (th->th_dbs) + hash_free(th->th_dbs); free(th); } retval = 0; @@ -245,37 +234,6 @@ text_setopt(xmldb_handle xh, return retval; } -/*! Populate with spec - * @param[in] xt XML tree with some node marked - */ -int -xml_spec_populate(cxobj *x, - void *arg) -{ - int retval = -1; - yang_spec *yspec = (yang_spec*)arg; - char *name; - yang_stmt *y; /* yang node */ - cxobj *xp; /* xml parent */ - yang_stmt *yp; /* parent yang */ - - name = xml_name(x); - if ((xp = xml_parent(x)) != NULL && - (yp = xml_spec(xp)) != NULL) - y = yang_find_syntax((yang_node*)yp, xml_name(x)); - else - y = yang_find_topnode(yspec, name); /* still NULL for config */ - if (y==NULL){ - clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", - name, - xp?xml_name(xp):"", yp?yp->ys_argument:""); - goto done; - } - xml_spec_set(x, y); - retval = 0; - done: - return retval; -} /*! Ensure that xt only has a single sub-element and that is "config" */ @@ -318,39 +276,15 @@ singleconfigroot(cxobj *xt, /*! Get content of database using xpath. return a set of matching sub-trees * The function returns a minimal tree that includes all sub-trees that match * xpath. - * @param[in] xh XMLDB handle - * @param[in] dbname Name of database to search in (filename including dir path - * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[out] xtop Single XML tree which xvec points to. Free with xml_free() - * @param[out] xvec Vector of xml trees. Free after use. - * @param[out] xlen Length of vector. - * @retval 0 OK - * @retval -1 Error - * @code - * cxobj *xt; - * cxobj **xvec; - * size_t xlen; - * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", - * &xt, &xvec, &xlen) < 0) - * err; - * for (i=0; ith_yangspec) == NULL){ + if ((yspec = th->th_yangspec) == NULL){ clicon_err(OE_YANG, ENOENT, "No yang spec"); goto done; } @@ -393,45 +327,44 @@ text_get(xmldb_handle xh, goto done; } /* Here xt looks like: ... */ - /* Validate existing config tree */ + /* Add yang specification backpointer to all XML nodes */ if (xml_apply(xt, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - /* XXX Maybe the below is general function and should be moved to xmldb? */ if (xpath_vec(xt, xpath?xpath:"/", &xvec, &xlen) < 0) goto done; - /* If vectors are specified then filter out everything else, + /* If vectors are specified then mark the nodes found and + * then filter out everything else, * otherwise return complete tree. */ if (xvec != NULL){ for (i=0; i1) clicon_xml2file(stderr, xt, 0, 1); - if (xvec0 && xlen0){ - *xvec0 = xvec; - xvec = NULL; - *xlen0 = xlen; - xlen = 0; - } *xtop = xt; xt = NULL; retval = 0; @@ -526,11 +459,10 @@ match_base_child(cxobj *x0, /*! Modify a base tree x0 with x1 with yang spec y according to operation op * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 * @param[in] x1 xml tree which modifies base * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc - * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL - * @param[in] yspec Top-level yang spec (if y is NULL) * Assume x0 and x1 are same on entry and that y is the spec * @see put in clixon_keyvalue.c */ @@ -733,32 +665,15 @@ text_modify_top(cxobj *x0, return retval; } - /*! Modify database provided an xml tree and an operation - * - * @param[in] xh XMLDB handle - * @param[in] db running or candidate - * @param[in] op OP_MERGE: just add it. - * OP_REPLACE: first delete whole database - * OP_NONE: operation attribute in xml determines operation - * @param[in] x1 xml-tree to merge/replace. Top-level symbol is 'config'. - * Should be empty or '' if delete? - * @retval 0 OK - * @retval -1 Error - * The xml may contain the "operation" attribute which defines the operation. - * @code - * cxobj *xt; - * if (clicon_xml_parse_str("17", &xt) < 0) - * err; - * if (xmldb_put(h, "running", OP_MERGE, "/", xt) < 0) - * err; - * @endcode -y */ + * This is a clixon datastore plugin of the the xmldb api + * @see xmldb_put + */ int text_put(xmldb_handle xh, char *db, enum operation_type op, - cxobj *x1) + cxobj *x1) { int retval = -1; struct text_handle *th = handle(xh); @@ -799,7 +714,6 @@ text_put(xmldb_handle xh, } /* 2. File is not empty ... -> replace root */ else{ - /* There should only be one element and called config */ if (singleconfigroot(x0, &x0) < 0) goto done; @@ -811,11 +725,11 @@ text_put(xmldb_handle xh, goto done; } - /* Validate existing config tree */ + /* Add yang specification backpointer to all XML nodes */ if (xml_apply(x0, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; - /* Validate modification tree */ + /* Add yang specification backpointer to all XML nodes */ if (xml_apply(x1, CX_ELMNT, xml_spec_populate, yspec) < 0) goto done; @@ -826,7 +740,7 @@ text_put(xmldb_handle xh, goto done; /* Remove NONE nodes if all subs recursively are also NONE */ - if (xml_tree_prune_flagged(x0, XML_FLAG_NONE, 0, NULL) <0) + if (xml_tree_prune_flagged_sub(x0, XML_FLAG_NONE, 0, NULL) <0) goto done; if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_NONE) < 0) @@ -871,8 +785,8 @@ text_put(xmldb_handle xh, */ int text_copy(xmldb_handle xh, - char *from, - char *to) + char *from, + char *to) { int retval = -1; struct text_handle *th = handle(xh); @@ -896,7 +810,7 @@ text_copy(xmldb_handle xh, } /*! Lock database - * @param[in] xh XMLDB handle + * @param[in] xh XMLDB handle * @param[in] db Database * @param[in] pid Process id * @retval -1 Error @@ -904,17 +818,12 @@ text_copy(xmldb_handle xh, */ int text_lock(xmldb_handle xh, - char *db, - int pid) + char *db, + int pid) { - // struct text_handle *th = handle(xh); + struct text_handle *th = 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; + hash_add(th->th_dbs, db, &pid, sizeof(pid)); clicon_debug(1, "%s: locked by %u", db, pid); return 0; } @@ -929,16 +838,13 @@ text_lock(xmldb_handle xh, */ int text_unlock(xmldb_handle xh, - char *db) + char *db) { - // struct text_handle *th = handle(xh); + struct text_handle *th = handle(xh); + int zero = 0; - 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; + hash_add(th->th_dbs, db, &zero, sizeof(zero)); + // hash_del(th->th_dbs, db); return 0; } @@ -952,14 +858,19 @@ int text_unlock_all(xmldb_handle xh, int pid) { - // struct text_handle *th = handle(xh); + struct text_handle *th = handle(xh); + char **keys; + size_t klen; + int i; + int *val; + size_t vlen; - if (_running_locked == pid) - _running_locked = 0; - if (_candidate_locked == pid) - _candidate_locked = 0; - if (_startup_locked == pid) - _startup_locked = 0; + if ((keys = hash_keys(th->th_dbs, &klen)) == NULL) + return 0; + for(i = 0; i < klen; i++) + if ((val = hash_value(th->th_dbs, keys[i], &vlen)) != NULL && + *val == pid) + hash_del(th->th_dbs, keys[i]); return 0; } @@ -974,14 +885,13 @@ int text_islocked(xmldb_handle xh, char *db) { - // struct text_handle *th = handle(xh); + struct text_handle *th = handle(xh); + size_t vlen; + int *val; - if (strcmp("running", db) == 0) - return (_running_locked); - else if (strcmp("candidate", db) == 0) - return(_candidate_locked); - else if (strcmp("startup", db) == 0) - return(_startup_locked); + if ((val = hash_value(th->th_dbs, db, &vlen)) == NULL) + return 0; + return *val; return 0; } @@ -993,8 +903,8 @@ text_islocked(xmldb_handle xh, * @retval 1 Yes it exists */ int -text_exists(xmldb_handle xh, - char *db) +text_exists(xmldb_handle xh, + char *db) { int retval = -1; @@ -1022,7 +932,7 @@ text_exists(xmldb_handle xh, */ int text_delete(xmldb_handle xh, - char *db) + char *db) { int retval = -1; char *filename = NULL; @@ -1049,7 +959,7 @@ text_delete(xmldb_handle xh, */ int text_create(xmldb_handle xh, - char *db) + char *db) { int retval = -1; struct text_handle *th = handle(xh); @@ -1078,6 +988,7 @@ text_plugin_exit(void) return 0; } +static const struct xmldb_api api; static const struct xmldb_api api; /*! plugin init function */ @@ -1167,7 +1078,7 @@ main(int argc, char **argv) if (argc < 5) usage(argv[0]); xpath = argc>5?argv[5]:NULL; - if (xmldb_get(h, db, xpath, &xt, NULL, NULL) < 0) + if (xmldb_get(h, db, xpath, &xt, NULL, 1, NULL) < 0) goto done; clicon_xml2file(stdout, xt, 0, 1); } diff --git a/datastore/text/clixon_xmldb_text.h b/datastore/text/clixon_xmldb_text.h index 7a53fdec..c3c24c22 100644 --- a/datastore/text/clixon_xmldb_text.h +++ b/datastore/text/clixon_xmldb_text.h @@ -39,8 +39,7 @@ /* * Prototypes */ -int text_get(xmldb_handle h, char *db, char *xpath, - cxobj **xtop, cxobj ***xvec, size_t *xlen); +int text_get(xmldb_handle h, char *db, char *xpath, int config, cxobj **xtop); int text_put(xmldb_handle h, char *db, enum operation_type op, cxobj *xt); int text_dump(FILE *f, char *dbfilename, char *rxkey); int text_copy(xmldb_handle h, char *from, char *to); diff --git a/doc/FAQ.md b/doc/FAQ.md index 8a57496f..a0cd18a9 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -56,7 +56,7 @@ use the web resource: http://clicon.org/ref/index.html ## How is configuration data stored? Configuration data is stored in an XML datastore. The default is a -text-based addatastore, but there also exists a key-value datastore +text-based datastore, but there also exists a key-value datastore using qdbm. In the example the datastore are regular files found in /usr/local/var/routing/. @@ -110,13 +110,33 @@ and then invoke it from a client using ssh -s netconf ``` +## How do I use restconf? + +You can access clixon via REST API using restconf, such as using +curl. GET, PUT, POST are supported. + +You need a web-server, such as nginx, and start a restconf fcgi +daemon, clixon_restconf. Read more in the restconf docs. + +Example: +``` + curl -G http://127.0.0.1/restconf/data/interfaces/interface/name=eth9/type + [ + { + "type": "eth" + } + ] +``` + ## How do I use notifications? The example has a prebuilt notification stream called "ROUTING" that triggers every 10s. -You enable the notification either via the cli or via netconf: +You enable the notification either via the cli: +``` cli> notify -cli> Routing notification -Routing notification +cli> +``` +or via netconf: ``` clixon_netconf -qf /usr/local/etc/routing.conf ROUTING]]>]]> @@ -141,7 +161,7 @@ backend. It has a 'transaction_data td' argument which is used to fetch information on added, deleted and changed entries. You access this information using access functions as defined in clixon_backend_transaction.h -## How do i check what has changed on commit? +## How do I check what has changed on commit? You use XPATHs on the XML trees in the transaction commit callback. Suppose you want to print all added interfaces: ``` @@ -181,3 +201,12 @@ Check for inconsistencies in the XML trees and if they fail, make an clicon_err( return -1; The validation or commit will then be aborted. +## How do I write a state data callback function? + +Netconf and restconf GET also returns state data, in contrast to +config data. In YANG state data is specified with "config false;". + +To return state data, you need to write a backend state data callback +with the name "plugin_statedata()" where you return an XML tree. + +Please look at the example for an example on how to write a state data callback. diff --git a/example/README.md b/example/README.md index 8c325b60..b833feee 100644 --- a/example/README.md +++ b/example/README.md @@ -81,8 +81,28 @@ cli> downcall "This is a string" This is a string ``` +## State data + +Netconf and restconf GET also returns state data, in contrast to +config data. + +In YANG state data is specified with "config false;". In the example, interface-state is state data. + +To return state data, you need to write a backend state data callback +with the name "plugin_statedata" where you return an XML tree with +state. This is then merged with config data by the system. + +pA static example of returning state data is in the example. Note that +a real example would poll or get the interface counters via a system +call, as well as use the "xpath" argument to identify the requested +state data. + + ## Run as docker container ``` cd docker # look in README -``` \ No newline at end of file +``` + + + diff --git a/example/ietf-ip@2014-06-16.yang b/example/ietf-ip@2014-06-16.yang index 8e39326d..ce235e12 100644 --- a/example/ietf-ip@2014-06-16.yang +++ b/example/ietf-ip@2014-06-16.yang @@ -1,4 +1,4 @@ - module ietf-ip { +module ietf-ip { namespace "urn:ietf:params:xml:ns:yang:ietf-ip"; prefix ip; diff --git a/example/routing_backend.c b/example/routing_backend.c index 34e6fc80..33a86c11 100644 --- a/example/routing_backend.c +++ b/example/routing_backend.c @@ -129,6 +129,40 @@ routing_downcall(clicon_handle h, cprintf(cbret, "%s", xml_body(xe)); return 0; } + +/*! Called to get state data from plugin + * @param[in] h Clicon handle + * @param[in] xpath String with XPATH syntax. or NULL for all + * @param[in] xtop XML tree, on entry. + * @retval 0 OK + * @retval -1 Error + * @see xmldb_get + */ +int +plugin_statedata(clicon_handle h, + char *xpath, + cxobj *xstate) +{ + int retval = -1; + cxobj **xvec = NULL; + + /* Example of (static) statedata, real code would poll state */ + if (0 && (xml_parse("" + "eth0" + "eth" + "up" + "up" + "42" + "1000000000" + "", xstate)) < 0) + goto done; + retval = 0; + done: + if (xvec) + free(xvec); + return retval; +} + /* * Plugin initialization */ @@ -139,10 +173,11 @@ plugin_init(clicon_handle h) if (notification_timer_setup(h) < 0) goto done; - if (backend_netconf_register_callback(h, routing_downcall, - NULL, - "myrouting"/* Xml tag when callback is made */ - ) < 0) + /* Register callback for netconf application-specific rpc call */ + if (backend_rpc_cb_register(h, routing_downcall, + NULL, + "myrouting"/* Xml tag when callback is made */ + ) < 0) goto done; retval = 0; done: diff --git a/example/routing_cli.c b/example/routing_cli.c index 3515408a..e2e67b2b 100644 --- a/example/routing_cli.c +++ b/example/routing_cli.c @@ -83,6 +83,7 @@ mycallback(clicon_handle h, cvec *cvv, cvec *argv) if (clicon_rpc_get_config(h, "running","/interfaces/interface[name=eth0]", &xret) < 0) goto done; + xml_print(stdout, xret); retval = 0; done: diff --git a/lib/clixon/clixon_proto_client.h b/lib/clixon/clixon_proto_client.h index c19997d1..ca690b9f 100644 --- a/lib/clixon/clixon_proto_client.h +++ b/lib/clixon/clixon_proto_client.h @@ -52,6 +52,7 @@ int clicon_rpc_copy_config(clicon_handle h, char *db1, char *db2); int clicon_rpc_delete_config(clicon_handle h, char *db); int clicon_rpc_lock(clicon_handle h, char *db); int clicon_rpc_unlock(clicon_handle h, char *db); +int clicon_rpc_get(clicon_handle h, char *xpath, cxobj **xret); int clicon_rpc_close_session(clicon_handle h); int clicon_rpc_kill_session(clicon_handle h, int session_id); int clicon_rpc_validate(clicon_handle h, char *db); diff --git a/lib/clixon/clixon_string.h b/lib/clixon/clixon_string.h index e0c9f1b7..ba60e681 100644 --- a/lib/clixon/clixon_string.h +++ b/lib/clixon/clixon_string.h @@ -36,6 +36,23 @@ #ifndef _CLIXON_STRING_H_ #define _CLIXON_STRING_H_ +/* Struct used to map between int and strings. Typically used to map between + * values and their names. Note NULL terminated + * Example: + * @code +static const map_str2int atmap[] = { + {"One", 1}, + {"Two", 2}, + {NULL, -1} +}; + * @endcode + */ +struct map_str2int{ + char *ms_str; + int ms_int; +}; +typedef struct map_str2int map_str2int; + /*! A malloc version that aligns on 4 bytes. To avoid warning from valgrind */ #define align4(s) (((s)/4)*4 + 4) @@ -59,6 +76,9 @@ char *clicon_strjoin (int argc, char **argv, char *delim); int str2cvec(char *string, char delim1, char delim2, cvec **cvp); int percent_encode(char *str, char **escp); int percent_decode(char *esc, char **str); +const char *clicon_int2str(const map_str2int *mstab, int i); +int clicon_str2int(const map_str2int *mstab, char *str); + #ifndef HAVE_STRNDUP char *clicon_strndup (const char *, size_t); #endif /* ! HAVE_STRNDUP */ diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index e98887ff..f4ed4c07 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -128,6 +128,7 @@ int clicon_xml_parse_file(int fd, cxobj **xml_top, char *endtag); #define clicon_xml_parse_string(str, x) clicon_xml_parse_str((*str), x) int clicon_xml_parse_str(char *str, cxobj **xml_top); int clicon_xml_parse(cxobj **cxtop, char *format, ...); +int xml_parse(char *str, cxobj *x_up); int xmltree2cbuf(cbuf *cb, cxobj *x, int level); int xml_copy(cxobj *x0, cxobj *x1); diff --git a/lib/clixon/clixon_xml_db.h b/lib/clixon/clixon_xml_db.h index 8cd645cb..23a04ca0 100644 --- a/lib/clixon/clixon_xml_db.h +++ b/lib/clixon/clixon_xml_db.h @@ -75,12 +75,10 @@ typedef int (xmldb_getopt_t)(xmldb_handle xh, char *optname, void **value); typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value); /* Type of xmldb get function */ -typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath, - cxobj **xtop, cxobj ***xvec, size_t *xlen); +typedef int (xmldb_get_t)(xmldb_handle xh, char *db, char *xpath, int config, cxobj **xtop); /* Type of xmldb put function */ -typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op, - cxobj *xt); +typedef int (xmldb_put_t)(xmldb_handle xh, char *db, enum operation_type op, cxobj *xt); /* Type of xmldb copy function */ typedef int (xmldb_copy_t)(xmldb_handle xh, char *from, char *to); @@ -135,12 +133,12 @@ struct xmldb_api{ int xmldb_plugin_load(clicon_handle h, char *filename); int xmldb_plugin_unload(clicon_handle h); +int xmldb_validate_db(char *db); int xmldb_connect(clicon_handle h); int xmldb_disconnect(clicon_handle h); int xmldb_getopt(clicon_handle h, char *optname, void **value); int xmldb_setopt(clicon_handle h, char *optname, void *value); -int xmldb_get(clicon_handle h, char *db, char *xpath, - cxobj **xtop, cxobj ***xvec, size_t *xlen); +int xmldb_get(clicon_handle h, char *db, char *xpath, int config, cxobj **xtop); int xmldb_put(clicon_handle h, char *db, enum operation_type op, cxobj *xt); int xmldb_copy(clicon_handle h, char *from, char *to); int xmldb_lock(clicon_handle h, char *db, int pid); diff --git a/lib/clixon/clixon_xml_map.h b/lib/clixon/clixon_xml_map.h index abcf26c5..aba551bf 100644 --- a/lib/clixon/clixon_xml_map.h +++ b/lib/clixon/clixon_xml_map.h @@ -48,7 +48,6 @@ enum { LVXML_VECVAL2, /* key: a.b.0{x=1} -> 1 och */ }; - /* * Prototypes */ @@ -64,12 +63,16 @@ int xml_diff(yang_spec *yspec, cxobj *xt1, cxobj *xt2, int yang2api_path_fmt(yang_stmt *ys, int inclkey, char **api_path_fmt); int api_path_fmt2api_path(char *api_path_fmt, cvec *cvv, char **api_path); int api_path_fmt2xpath(char *api_path_fmt, cvec *cvv, char **xpath); -int xml_tree_prune_flagged(cxobj *xt, int flag, int test, int *upmark); +int xml_tree_prune_flagged_sub(cxobj *xt, int flag, int test, int *upmark); +int xml_tree_prune_flagged(cxobj *xt, int flag, int test); 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(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); int api_path2xml(char *api_path, yang_spec *yspec, cxobj *xtop, cxobj **xpathp, yang_node **ypathp); +int xml_merge(cxobj *x0, cxobj *x1, yang_spec *yspec); #endif /* _CLIXON_XML_MAP_H_ */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index 332b43dc..79838b36 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -199,7 +199,7 @@ char *ytype_prefix(yang_stmt *ys); char *ytype_id(yang_stmt *ys); yang_stmt *ys_module(yang_stmt *ys); yang_spec *ys_spec(yang_stmt *ys); -yang_stmt *ys_module_import(yang_stmt *ymod, char *prefix); +yang_stmt *yang_find_module_by_prefix(yang_stmt *ys, char *prefix); yang_stmt *yang_find(yang_node *yn, int keyword, char *argument); yang_stmt *yang_find_syntax(yang_node *yn, char *argument); yang_stmt *yang_find_topnode(yang_spec *ysp, char *name); diff --git a/lib/src/clixon_hash.c b/lib/src/clixon_hash.c index fc914b60..3ad70036 100644 --- a/lib/src/clixon_hash.c +++ b/lib/src/clixon_hash.c @@ -291,7 +291,7 @@ hash_del(clicon_hash_t *hash, return 0; } -/*! Return vector of keys in has table +/*! Return vector of keys in hash table * * @param[in] hash Hash table * @param[out] nkeys Size of key vector diff --git a/lib/src/clixon_proto_client.c b/lib/src/clixon_proto_client.c index e21d9600..9c111676 100644 --- a/lib/src/clixon_proto_client.c +++ b/lib/src/clixon_proto_client.c @@ -230,16 +230,22 @@ clicon_rpc_generate_error(cxobj *xerr) * @param[in] h CLICON handle * @param[in] db Name of database * @param[in] xpath XPath (or "") - * @param[out] xt XML tree. must be freed by caller with xml_free + * @param[out] xt XML tree. Free with xml_free. + * Either or . * @retval 0 OK * @retval -1 Error, fatal or xml * @code * cxobj *xt = NULL; * if (clicon_rpc_get_config(h, "running", "/", &xt) < 0) * err; + * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + * clicon_rpc_generate_error(xerr); + * err; + * } * if (xt) * xml_free(xt); * @endcode + * @see clicon_rpc_generate_error */ int clicon_rpc_get_config(clicon_handle h, @@ -251,7 +257,6 @@ clicon_rpc_get_config(clicon_handle h, struct clicon_msg *msg = NULL; cbuf *cb = NULL; cxobj *xret = NULL; - cxobj *xerr; cxobj *xd; if ((cb = cbuf_new()) == NULL) @@ -264,11 +269,10 @@ clicon_rpc_get_config(clicon_handle h, goto done; if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) goto done; - if ((xerr = xpath_first(xret, "//rpc-error")) != NULL){ - clicon_rpc_generate_error(xerr); - goto done; - } - if ((xd = xpath_first(xret, "//data/config")) == NULL) + /* Send xml error back: first check error, then ok */ + if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL) + xd = xml_parent(xd); /* point to rpc-reply */ + else if ((xd = xpath_first(xret, "/rpc-reply/data/config")) == NULL) if ((xd = xml_new("config", NULL)) == NULL) goto done; if (xt){ @@ -472,6 +476,70 @@ clicon_rpc_unlock(clicon_handle h, return retval; } +/*! Get database configuration and state data + * Same as clicon_proto_change just with a cvec instead of lvec + * @param[in] h CLICON handle + * @param[in] xpath XPath (or "") + * @param[out] xt XML tree. Free with xml_free. + * Either or . + * @retval 0 OK + * @retval -1 Error, fatal or xml + * @code + * cxobj *xt = NULL; + * if (clicon_rpc_get(h, "/", &xt) < 0) + * err; + * if ((xerr = xpath_first(xt, "/rpc-error")) != NULL){ + * clicon_rpc_generate_error(xerr); + * err; + * } + * if (xt) + * xml_free(xt); + * @endcode + * @see clicon_rpc_generate_error + */ +int +clicon_rpc_get(clicon_handle h, + char *xpath, + cxobj **xt) +{ + int retval = -1; + struct clicon_msg *msg = NULL; + cbuf *cb = NULL; + cxobj *xret = NULL; + cxobj *xd; + + if ((cb = cbuf_new()) == NULL) + goto done; + cprintf(cb, ""); + if (xpath && strlen(xpath)) + cprintf(cb, "", xpath); + cprintf(cb, ""); + if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) == NULL) + goto done; + if (clicon_rpc_msg(h, msg, &xret, NULL) < 0) + goto done; + /* Send xml error back: first check error, then ok */ + if ((xd = xpath_first(xret, "/rpc-reply/rpc-error")) != NULL) + xd = xml_parent(xd); /* point to rpc-reply */ + else if ((xd = xpath_first(xret, "/rpc-reply/data/config")) == NULL) + if ((xd = xml_new("config", NULL)) == NULL) + goto done; + if (xt){ + if (xml_rm(xd) < 0) + goto done; + *xt = xd; + } + retval = 0; + done: + if (cb) + cbuf_free(cb); + if (xret) + xml_free(xret); + if (msg) + free(msg); + return retval; +} + /*! Close a (user) session * @param[in] h CLICON handle */ diff --git a/lib/src/clixon_string.c b/lib/src/clixon_string.c index 06b9fc49..6ee4ee67 100644 --- a/lib/src/clixon_string.c +++ b/lib/src/clixon_string.c @@ -333,6 +333,43 @@ str2cvec(char *string, goto done; } +/*! Map from int to string using str2int map + * @param[in] ms String, integer map + * @param[in] i Input integer + * @retval str String value + * @retval NULL Error, not found + * @note linear search + */ +const char * +clicon_int2str(const map_str2int *mstab, + int i) +{ + const struct map_str2int *ms; + + for (ms = &mstab[0]; ms->ms_str; ms++) + if (ms->ms_int == i) + return ms->ms_str; + return NULL; +} + +/*! Map from string to int using str2int map + * @param[in] ms String, integer map + * @param[in] str Input string + * @retval int Value + * @retval -1 Error, not found + * @note linear search + */ +int +clicon_str2int(const map_str2int *mstab, + char *str) +{ + const struct map_str2int *ms; + + for (ms = &mstab[0]; ms->ms_str; ms++) + if (strcmp(ms->ms_str, str) == 0) + return ms->ms_int; + return -1; +} /*! strndup() for systems without it, such as xBSD */ diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 0b4d3835..44a9dcf4 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -50,6 +50,7 @@ /* clixon */ #include "clixon_err.h" #include "clixon_log.h" +#include "clixon_string.h" #include "clixon_queue.h" #include "clixon_xml.h" #include "clixon_xml_parse.h" @@ -82,14 +83,8 @@ struct xml{ cg_var *x_cv; /* If body this contains the typed value */ }; -/* Type to string conversion */ -struct map_str2int{ - char *ms_str; - enum cxobj_type ms_type; -}; - /* Mapping between xml type <--> string */ -static const struct map_str2int xsmap[] = { +static const map_str2int xsmap[] = { {"error", CX_ERROR}, {"element", CX_ELMNT}, {"attr", CX_ATTR}, @@ -104,12 +99,7 @@ static const struct map_str2int xsmap[] = { char * xml_type2str(enum cxobj_type type) { - const struct map_str2int *xs; - - for (xs = &xsmap[0]; xs->ms_str; xs++) - if (xs->ms_type == type) - return xs->ms_str; - return NULL; + return (char*)clicon_int2str(xsmap, type); } /* @@ -476,9 +466,14 @@ xml_childvec_get(cxobj *x) * * @param[in] name Name of new * @param[in] xp The parent where the new xml node should be inserted - * - * @retval created xml object if successful - * @retval NULL if error and clicon_err() called + * @retval xml Created xml object if successful + * @retval NULL Error and clicon_err() called + * @code + * cxobj *x; + * if ((x = xml_new(name, xparent)) == NULL) + * err; + * @endcode + * @see xml_new_spec Also sets yang spec. */ cxobj * xml_new(char *name, @@ -521,7 +516,6 @@ xml_new_spec(char *name, return x; } - void * xml_spec(cxobj *x) { @@ -971,10 +965,12 @@ clicon_xml2cbuf(cbuf *cb, return 0; } -/*! Internal xml parsing function. +/*! Basic xml parsing function. + * @param[in] str Pointer to string containing XML definition. + * @param[out] xtop Top of XML parse tree. Assume created. * @see clicon_xml_parse_file clicon_xml_parse_string */ -static int +int xml_parse(char *str, cxobj *x_up) { @@ -1191,7 +1187,7 @@ clicon_xml_parse_str(char *str, */ int clicon_xml_parse(cxobj **cxtop, - char *format, ...) + char *format, ...) { int retval = -1; va_list args; @@ -1355,6 +1351,10 @@ cxvec_append(cxobj *x, * @param[in] type matching type or -1 for any * @param[in] fn Callback * @param[in] arg Argument + * @retval -1 Error, aborted at first error encounter + * @retval 0 OK, all nodes traversed + * @retval n OK, aborted at first encounter of first match + * * @code * int x_fn(cxobj *x, void *arg) * { @@ -1363,7 +1363,7 @@ cxvec_append(cxobj *x, * xml_apply(xn, CX_ELMNT, x_fn, NULL); * @endcode * @note do not delete or move around any children during this function - * @note It does not apply fn to the root node,.. + * @note return value > 0 aborts the traversal * @see xml_apply0 including top object */ int @@ -1373,13 +1373,19 @@ xml_apply(cxobj *xn, void *arg) { int retval = -1; - cxobj *x = NULL; + cxobj *x; + int ret; + x = NULL; while ((x = xml_child_each(xn, x, type)) != NULL) { if (fn(x, arg) < 0) goto done; - if (xml_apply(x, type, fn, arg) < 0) + if ((ret = xml_apply(x, type, fn, arg)) < 0) goto done; + if (ret > 0){ + retval = ret; + goto done; + } } retval = 0; done: @@ -1387,7 +1393,11 @@ xml_apply(cxobj *xn, } /*! Apply a function call on top object and all xml node children recursively + * @retval -1 Error, aborted at first error encounter + * @retval 0 OK, all nodes traversed + * @retval n OK, aborted at first encounter of first match * @see xml_apply not including top object + */ int xml_apply0(cxobj *xn, @@ -1396,10 +1406,14 @@ xml_apply0(cxobj *xn, void *arg) { int retval = -1; + int ret; - if (fn(xn, arg) < 0) + if ((ret = fn(xn, arg)) < 0) goto done; - retval = xml_apply(xn, type, fn, arg); + if (ret > 0) + retval = ret; + else + retval = xml_apply(xn, type, fn, arg); done: return retval; } @@ -1412,6 +1426,9 @@ xml_apply0(cxobj *xn, * @param[in] xn XML node * @param[in] fn Callback * @param[in] arg Argument + * @retval -1 Error, aborted at first error encounter + * @retval 0 OK, all nodes traversed + * @retval n OK, aborted at first encounter of first match * @code * int x_fn(cxobj *x, void *arg) * { @@ -1430,12 +1447,17 @@ xml_apply_ancestor(cxobj *xn, { int retval = -1; cxobj *xp = NULL; + int ret; while ((xp = xml_parent(xn)) != NULL) { if (fn(xp, arg) < 0) goto done; - if (xml_apply_ancestor(xp, fn, arg) < 0) + if ((ret = xml_apply_ancestor(xp, fn, arg)) < 0) goto done; + if (ret > 0){ + retval = ret; + goto done; + } xn = xp; } retval = 0; diff --git a/lib/src/clixon_xml_db.c b/lib/src/clixon_xml_db.c index 0ed0bafe..19b2e12e 100644 --- a/lib/src/clixon_xml_db.c +++ b/lib/src/clixon_xml_db.c @@ -169,6 +169,23 @@ xmldb_plugin_unload(clicon_handle h) return retval; } +/*! Validate database name + * @param[in] db Name of database + * @param[out] xret Return value as cligen buffer containing xml netconf return + * @retval 0 OK + * @retval -1 Failed validate, xret set to error + */ +int +xmldb_validate_db(char *db) +{ + if (strcmp(db, "running") != 0 && + strcmp(db, "candidate") != 0 && + strcmp(db, "startup") != 0 && + strcmp(db, "tmp") != 0) + return -1; + return 0; +} + /*! Connect to a datastore plugin, allocate handle to be used in API calls * @param[in] h Clicon handle * @retval 0 OK @@ -305,36 +322,25 @@ xmldb_setopt(clicon_handle h, * @param[in] h Clicon handle * @param[in] dbname Name of database to search in (filename including dir path * @param[in] xpath String with XPATH syntax. or NULL for all - * @param[out] xtop Single XML tree which xvec points to. Free with xml_free() - * @param[out] xvec Vector of xml trees. Free after use. - * @param[out] xlen Length of vector. + * @param[in] config If set only configuration data, else also state + * @param[out] xtop Single XML tree. Free with xml_free() * @retval 0 OK * @retval -1 Error * @code * cxobj *xt; - * cxobj **xvec; - * size_t xlen; - * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", - * &xt, &xvec, &xlen) < 0) + * if (xmldb_get(xh, "running", "/interfaces/interface[name="eth"]", 1, &xt) < 0) * err; - * for (i=0; ixa_get_fn(xh, db, xpath, xtop, xvec, xlen); + retval = xa->xa_get_fn(xh, db, xpath, config, xtop); #if DEBUG if (retval == 0) { cbuf *cb = cbuf_new(); @@ -366,14 +372,12 @@ xmldb_get(clicon_handle h, return retval; } -/*! Modify database provided an xml tree and an operation +/*! Modify database given an xml tree and an operation * * @param[in] h CLICON handle * @param[in] db running or candidate + * @param[in] op Top-level operation, can be superceded by other op in tree * @param[in] xt xml-tree. Top-level symbol is dummy - * @param[in] op OP_MERGE: just add it. - * OP_REPLACE: first delete whole database - * OP_NONE: operation attribute in xml determines operation * @retval 0 OK * @retval -1 Error * The xml may contain the "operation" attribute which defines the operation. @@ -384,6 +388,9 @@ xmldb_get(clicon_handle h, * if (xmldb_put(xh, "running", OP_MERGE, xt) < 0) * err; * @endcode + * @note that you can add both config data and state data. In comparison, + * xmldb_get has a parameter to get config data only. + * */ int xmldb_put(clicon_handle h, @@ -581,7 +588,7 @@ xmldb_islocked(clicon_handle h, clicon_err(OE_DB, 0, "Not connected to datastore plugin"); goto done; } - retval =xa->xa_islocked_fn(xh, db); + retval = xa->xa_islocked_fn(xh, db); done: return retval; } diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 982aa3b3..3e2ebc45 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -1084,23 +1084,25 @@ api_path_fmt2xpath(char *api_path_fmt, return retval; } -/*! Prune everything that does not pass test +/*! Prune everything that does not pass test or have at least a child* does not * @param[in] xt XML tree with some node marked * @param[in] flag Which flag to test for * @param[in] test 1: test that flag is set, 0: test that flag is not set * @param[out] upmark Set if a child (recursively) has marked set. - * The function removes all branches that does not a child that pass the test + * The function removes all branches that does not pass the test * Purge all nodes that dont have MARK flag set recursively. * Save all nodes that is MARK:ed or have at least one (grand*)child that is MARKed * @code - * xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL); + * xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL); * @endcode + * @note This function seems a little too complex semantics + * @see xml_tree_prune_flagged for a simpler variant */ int -xml_tree_prune_flagged(cxobj *xt, - int flag, - int test, - int *upmark) +xml_tree_prune_flagged_sub(cxobj *xt, + int flag, + int test, + int *upmark) { int retval = -1; int submark; @@ -1117,6 +1119,7 @@ xml_tree_prune_flagged(cxobj *xt, xprev = x = NULL; while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { if (xml_flag(x, flag) == test?flag:0){ + /* Pass test */ mark++; xprev = x; continue; /* mark and stop here */ @@ -1131,7 +1134,7 @@ xml_tree_prune_flagged(cxobj *xt, continue; } } - if (xml_tree_prune_flagged(x, flag, test, &submark) < 0) + if (xml_tree_prune_flagged_sub(x, flag, test, &submark) < 0) goto done; /* if xt is list and submark anywhere, then key subs are also marked */ @@ -1167,6 +1170,43 @@ xml_tree_prune_flagged(cxobj *xt, return retval; } + +/*! Prune everything that passes test + * @param[in] xt XML tree with some node marked + * @param[in] flag Which flag to test for + * @param[in] test 1: test that flag is set, 0: test that flag is not set + * The function removes all branches that does not pass test + * @code + * xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1, NULL); + * @endcode + */ +int +xml_tree_prune_flagged(cxobj *xt, + int flag, + int test) +{ + int retval = -1; + cxobj *x; + cxobj *xprev; + + x = NULL; + xprev = x = NULL; + while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) { + if (xml_flag(x, flag) == test?flag:0){ /* Pass test means purge */ + if (xml_purge(x) < 0) + goto done; + x = xprev; + continue; + } + if (xml_tree_prune_flagged(x, flag, test) < 0) + goto done; + xprev = x; + } + retval = 0; + done: + return retval; +} + /*! Add default values (if not set) * @param[in] xt XML tree with some node marked */ @@ -1293,10 +1333,6 @@ xml_sanity(cxobj *xt, goto done; } name = xml_name(xt); - if (ys==NULL){ - clicon_err(OE_XML, 0, "No spec for xml node %s", name); - goto done; - } if (strstr(ys->ys_argument, name)==NULL){ clicon_err(OE_XML, 0, "xml node name '%s' does not match yang spec arg '%s'", name, ys->ys_argument); @@ -1307,6 +1343,69 @@ xml_sanity(cxobj *xt, return retval; } +/*! Mark all nodes that are not configure data and set return + * @param[in] xt XML tree + * @param[out] arg If set, set to 1 as int* if not config data + */ +int +xml_non_config_data(cxobj *xt, + void *arg) /* Set to 1 if state node */ +{ + int retval = -1; + yang_stmt *ys; + + if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){ + clicon_log(LOG_WARNING, "%s: no xml_spec(%s)", __FUNCTION__, xml_name(xt)); + retval = 0; + goto done; + } + if (!yang_config(ys)){ /* config == false means state data: mark for remove */ + xml_flag_set(xt, XML_FLAG_MARK); + if (arg) + (*(int*)arg) = 1; + } + retval = 0; + done: + return retval; +} + +/*! Add yang specification backpoint to XML node + * @param[in] xt XML tree node + * @note This should really be unnecessary since yspec should be set on creation + * @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; + char *name; + yang_stmt *y; /* yang node */ + cxobj *xp; /* xml parent */ + yang_stmt *yp; /* parent yang */ + + name = xml_name(x); + if ((xp = xml_parent(x)) != NULL && + (yp = xml_spec(xp)) != NULL) + y = yang_find_syntax((yang_node*)yp, xml_name(x)); + else + y = yang_find_topnode(yspec, name); /* still NULL for config */ + if (y==NULL){ + clicon_err(OE_XML, EBADF, "yang spec not found for xml node '%s' xml parent name: '%s' yangspec:'%s']", + name, + xp?xml_name(xp):"", yp?yp->ys_argument:""); + goto done; + } + xml_spec_set(x, y); + 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 @@ -1617,3 +1716,195 @@ api_path2xml(char *api_path, free(vec); return retval; } + +/*! Given a modification tree, check existing matching child in the base tree + * param[in] x0 Base tree node + * param[in] x1c Modification tree child + * param[in] yc Yang spec of tree child + * param[out] x0cp Matching base tree child (if any) +*/ +static int +match_base_child(cxobj *x0, + cxobj *x1c, + yang_stmt *yc, + cxobj **x0cp) +{ + int retval = -1; + cxobj *x0c = NULL; + char *keyname; + cvec *cvk = NULL; + cg_var *cvi; + char *b0; + char *b1; + yang_stmt *ykey; + char *cname; + int ok; + char *x1bstr; /* body string */ + + cname = xml_name(x1c); + switch (yc->ys_keyword){ + case Y_LEAF_LIST: /* Match with name and value */ + x1bstr = xml_body(x1c); + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { + if (strcmp(cname, xml_name(x0c)) == 0 && + strcmp(xml_body(x0c), x1bstr)==0) + break; + } + break; + case Y_LIST: /* Match with key values */ + if ((ykey = yang_find((yang_node*)yc, Y_KEY, NULL)) == NULL){ + clicon_err(OE_XML, errno, "%s: List statement \"%s\" has no key", + __FUNCTION__, yc->ys_argument); + goto done; + } + /* The value is a list of keys: [ ]* */ + if ((cvk = yang_arg2cvec(ykey, " ")) == NULL) + goto done; + x0c = NULL; + while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL) { + if (strcmp(xml_name(x0c), cname)) + continue; + cvi = NULL; + ok = 0; + while ((cvi = cvec_each(cvk, cvi)) != NULL) { + keyname = cv_string_get(cvi); + ok = 1; /* if we come here */ + if ((b0 = xml_find_body(x0c, keyname)) == NULL) + break; /* error case */ + if ((b1 = xml_find_body(x1c, keyname)) == NULL) + break; /* error case */ + if (strcmp(b0, b1)) + break; + ok = 2; /* and reaches here for all keynames, x0c is found. */ + } + if (ok == 2) + break; + } + break; + default: /* Just match with name */ + x0c = xml_find(x0, cname); + break; + } + *x0cp = x0c; + retval = 0; + done: + if (cvk) + cvec_free(cvk); + return retval; +} + +/*! Merge a base tree x0 with x1 with yang spec y + * @param[in] x0 Base xml tree (can be NULL in add scenarios) + * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL + * @param[in] x0p Parent of x0 + * @param[in] x1 xml tree which modifies base + * 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, + yang_node *y0, + cxobj *x0p, + cxobj *x1) +{ + int retval = -1; + char *x1name; + char *x1cname; /* child name */ + cxobj *x0c; /* base child */ + cxobj *x0b; /* base body */ + cxobj *x1c; /* mod child */ + char *x1bstr; /* mod body string */ + yang_stmt *yc; /* yang child */ + + assert(x1 && xml_type(x1) == CX_ELMNT); + assert(y0); + + x1name = xml_name(x1); + if (y0->yn_keyword == Y_LEAF_LIST || y0->yn_keyword == Y_LEAF){ + x1bstr = xml_body(x1); + if (x0==NULL){ + if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) + goto done; + if (x1bstr){ /* empty type does not have body */ + if ((x0b = xml_new("body", x0)) == NULL) + goto done; + xml_type_set(x0b, CX_BODY); + } + } + if (x1bstr){ + if ((x0b = xml_body_get(x0)) == NULL){ + if ((x0b = xml_new("body", x0)) == NULL) + goto done; + xml_type_set(x0b, CX_BODY); + } + if (xml_value_set(x0b, x1bstr) < 0) + goto done; + } + + } /* if LEAF|LEAF_LIST */ + else { /* eg Y_CONTAINER, Y_LIST */ + if (x0==NULL){ + if ((x0 = xml_new_spec(x1name, x0p, y0)) == NULL) + goto done; + } + /* Loop through children of the modification tree */ + x1c = NULL; + while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) { + x1cname = xml_name(x1c); + /* Get yang spec of the child */ + if ((yc = yang_find_syntax(y0, x1cname)) == NULL){ + clicon_err(OE_YANG, errno, "No yang node found: %s", x1cname); + goto done; + } + /* See if there is a corresponding node in the base tree */ + x0c = NULL; + if (yc && match_base_child(x0, x1c, yc, &x0c) < 0) + goto done; + if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) + goto done; + } + } /* else Y_CONTAINER */ + // ok: + retval = 0; + done: + return retval; +} + +/*! Merge XML trees x1 into x0 according to yang spec yspec + * @note both x0 and x1 need to be top-level trees + * @see text_modify_top as more generic variant (in datastore text) + */ +int +xml_merge(cxobj *x0, + cxobj *x1, + yang_spec *yspec) +{ + int retval = -1; + char *x1cname; /* child name */ + cxobj *x0c; /* base child */ + cxobj *x1c; /* mod child */ + yang_stmt *yc; + + /* Assure top-levels are 'config' */ + assert(x0 && strcmp(xml_name(x0),"config")==0); + assert(x1 && strcmp(xml_name(x1),"config")==0); + /* Loop through children of the modification tree */ + x1c = NULL; + 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)) == NULL){ + clicon_err(OE_YANG, ENOENT, "No yang spec"); + goto done; + } + /* See if there is a corresponding node in the base tree */ + if (match_base_child(x0, x1c, yc, &x0c) < 0) + goto done; + if (xml_merge1(x0c, (yang_node*)yc, x0, x1c) < 0) + goto done; + } + retval = 0; + done: + return retval; +} diff --git a/lib/src/clixon_xsl.c b/lib/src/clixon_xsl.c index b16da920..5928a4c6 100644 --- a/lib/src/clixon_xsl.c +++ b/lib/src/clixon_xsl.c @@ -100,6 +100,7 @@ in /* clicon */ #include "clixon_err.h" #include "clixon_log.h" +#include "clixon_string.h" #include "clixon_xml.h" #include "clixon_xsl.h" @@ -130,13 +131,8 @@ enum axis_type{ A_DESCENDANT_OR_SELF, /* actually descendant-or-self */ }; -struct map_str2int{ - char *ms_str; /* string as in 4.2.4 in RFC 6020 */ - int ms_int; -}; - /* Mapping between axis type string <--> int */ -static const struct map_str2int atmap[] = { +static const map_str2int axismap[] = { {"self", A_SELF}, {"child", A_CHILD}, {"parent", A_PARENT}, @@ -160,19 +156,6 @@ struct xpath_element{ static int xpath_split(char *xpathstr, char **pathexpr); -static char *axis_type2str(enum axis_type type) __attribute__ ((unused)); - -static char * -axis_type2str(enum axis_type type) -{ - const struct map_str2int *at; - - for (at = &atmap[0]; at->ms_str; at++) - if (at->ms_int == type) - return at->ms_str; - return NULL; -} - static int xpath_print(FILE *f, struct xpath_element *xplist) { @@ -180,7 +163,7 @@ xpath_print(FILE *f, struct xpath_element *xplist) struct xpath_predicate *xp; for (xe=xplist; xe; xe=xe->xe_next){ - fprintf(f, "\t:%s %s ", axis_type2str(xe->xe_type), + fprintf(f, "\t:%s %s ", clicon_int2str(axismap, xe->xe_type), xe->xe_str?xe->xe_str:""); for (xp=xe->xe_predicate; xp; xp=xp->xp_next) fprintf(f, "[%s]", xp->xp_expr); @@ -598,7 +581,7 @@ xpath_find(struct xpath_element *xe, } #if 0 fprintf(stderr, "%s: %s: \"%s\"\n", __FUNCTION__, - axis_type2str(xe->xe_type), xe->xe_str?xe->xe_str:""); + clicon_int2str(axismap, xe->xe_type), xe->xe_str?xe->xe_str:""); #endif switch (xe->xe_type){ case A_SELF: @@ -942,12 +925,12 @@ xpath_each(cxobj *cxtop, * @retval -1 error. * * @code - * cxobj **vec; - * size_t veclen; - * if (xpath_vec(cxtop, "//symbol/foo", &vec, &veclen) < 0) + * cxobj **xvec; + * size_t xlen; + * if (xpath_vec(cxtop, "//symbol/foo", &xvec, &xlen) < 0) * goto err; - * for (i=0; i clicon constants */ -static const struct map_str2int ykmap[] = { +static const map_str2int ykmap[] = { {"anyxml", Y_ANYXML}, {"argument", Y_ARGUMENT}, {"augment", Y_AUGMENT}, @@ -545,18 +529,10 @@ ys_flag_reset(yang_stmt *ys, return 0; } -/*! Translate from RFC 6020 keywords to printable string. - linear search,... - */ char * yang_key2str(int keyword) { - const struct map_str2int *yk; - - for (yk = &ykmap[0]; yk->ms_str; yk++) - if (yk->ms_int == keyword) - return yk->ms_str; - return NULL; + return (char*)clicon_int2str(ykmap, keyword); } /*! Find top module or sub-module. Note that ultimate top is yang spec @@ -635,29 +611,63 @@ ytype_prefix(yang_stmt *ys) return prefix; } -/*! Given a module and a prefix, find the import statement fo that prefix + +/*! Given a yang statement and a prefix, return yang module to that prefix * Note, not the other module but the proxy import statement only - * @param[in] ytop yang module + * @param[in] ys A yang statement + * @param[in] prefix prefix + * @retval ymod Yang module statement if found + * @retval NULL not found */ yang_stmt * -ys_module_import(yang_stmt *ymod, - char *prefix) +yang_find_module_by_prefix(yang_stmt *ys, + char *prefix) { - yang_stmt *yimport = NULL; + yang_stmt *yimport; yang_stmt *yprefix; + yang_stmt *my_ymod; + yang_stmt *ymod = NULL; + yang_spec *yspec; - assert(ymod->ys_keyword == Y_MODULE || ymod->ys_keyword == Y_SUBMODULE); - while ((yimport = yn_each((yang_node*)ymod, yimport)) != NULL) { + if ((my_ymod = ys_module(ys)) == NULL){ + clicon_err(OE_YANG, 0, "My yang module not found"); + goto done; + } + if ((yspec = ys_spec(my_ymod)) == NULL){ + clicon_err(OE_YANG, 0, "My yang spec not found"); + goto done; + } + if (my_ymod->ys_keyword != Y_MODULE && + my_ymod->ys_keyword != Y_SUBMODULE){ + clicon_err(OE_YANG, 0, "%s not module or sub-module", my_ymod->ys_argument); + goto done; + } + if ((yprefix = yang_find((yang_node*)my_ymod, Y_PREFIX, NULL)) != NULL && + strcmp(yprefix->ys_argument, prefix) == 0){ + ymod = my_ymod; + goto done; + } + yimport = NULL; + while ((yimport = yn_each((yang_node*)my_ymod, yimport)) != NULL) { if (yimport->ys_keyword != Y_IMPORT) continue; if ((yprefix = yang_find((yang_node*)yimport, Y_PREFIX, NULL)) != NULL && - strcmp(yprefix->ys_argument, prefix) == 0) - return yimport; + strcmp(yprefix->ys_argument, prefix) == 0){ + break; + } } - return NULL; + if (yimport){ + if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){ + clicon_err(OE_YANG, 0, "No module or sub-module found with prefix %s", + yimport->ys_argument); + yimport = NULL; + goto done; /* unresolved */ + } + } + done: + return ymod; } - /*! string is quoted if it contains space or tab, needs double '' */ static int inline quotedstring(char *s) @@ -1056,22 +1066,13 @@ ys_grouping_resolve(yang_stmt *ys, yang_stmt **ygrouping0) { int retval = -1; - yang_stmt *yimport; - yang_spec *yspec; yang_stmt *ymodule; yang_stmt *ygrouping = NULL; yang_node *yn; - yang_stmt *ymod; /* find the grouping associated with argument and expand(?) */ if (prefix){ /* Go to top and find import that matches */ - ymod = ys_module(ys); - if ((yimport = ys_module_import(ymod, prefix)) == NULL){ - clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix); - goto done; - } - yspec = ys_spec(ys); - if ((ymodule = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) != NULL) + if ((ymodule = yang_find_module_by_prefix(ys, prefix)) != NULL) ygrouping = yang_find((yang_node*)ymodule, Y_GROUPING, name); } else @@ -1087,7 +1088,7 @@ ys_grouping_resolve(yang_stmt *ys, } *ygrouping0 = ygrouping; retval = 0; - done: + // done: return retval; } @@ -1266,8 +1267,8 @@ yang_expand(yang_node *yn) * @param str String of yang statements * @param name Log string, typically filename * @param ysp Yang specification. Should ave been created by caller using yspec_new - * @retval 0 Everything OK - * @retval -1 Error encountered + * @retval ymod Top-level yang (sub)module + * @retval NULL Error encountered * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree * yang_parse1 # Parse one yang module, go through its (sub)modules and parse them @@ -1283,7 +1284,7 @@ yang_parse_str(clicon_handle h, yang_spec *yspec) { struct clicon_yang_yacc_arg yy = {0,}; - yang_stmt *ym = NULL; + yang_stmt *ymod = NULL; yy.yy_handle = h; yy.yy_name = (char*)name; @@ -1312,10 +1313,10 @@ yang_parse_str(clicon_handle h, if (yang_scan_exit(&yy) < 0) goto done; } - ym = yy.yy_module; + ymod = yy.yy_module; done: ystack_pop(&yy); - return ym; + return ymod; /* top-level (sub)module */ } /*! Read an opened file into a string and call yang string parsing @@ -1326,8 +1327,8 @@ yang_parse_str(clicon_handle h, * @param f Open file handle * @param name Log string, typically filename * @param ysp Yang specification. Should ave been created by caller using yspec_new - * @retval 0 Everything OK - * @retval -1 Error encountered + * @retval ymod Top-level yang (sub)module + * @retval NULL Error encountered * The database symbols are inserted in alphabetical order. * Calling order: @@ -1349,7 +1350,7 @@ yang_parse_file(clicon_handle h, int i; int c; int len; - yang_stmt *ymodule = NULL; + yang_stmt *ymod = NULL; clicon_debug(1, "Yang parse file: %s", name); len = 1024; /* any number is fine */ @@ -1373,12 +1374,12 @@ yang_parse_file(clicon_handle h, } buf[i++] = (char)(c&0xff); } /* read a line */ - if ((ymodule = yang_parse_str(h, buf, name, ysp)) < 0) + if ((ymod = yang_parse_str(h, buf, name, ysp)) < 0) goto done; done: if (buf) free(buf); - return ymodule; + return ymod; /* top-level (sub)module */ } /*! No specific revision give. Match a yang file given dir and module @@ -1387,7 +1388,7 @@ yang_parse_file(clicon_handle h, * @param[in] module Name of main YANG module. * @param[out] fbuf Buffer containing filename * - * @retval 1 Match founbd, Most recent entry returned in fbuf + * @retval 1 Match found, Most recent entry returned in fbuf * @retval 0 No matching entry found * @retval -1 Error */ @@ -1401,17 +1402,20 @@ yang_parse_find_match(clicon_handle h, struct dirent *dp = NULL; int ndp; cbuf *regex = NULL; - char *regexstr; if ((regex = cbuf_new()) == NULL){ clicon_err(OE_YANG, errno, "cbuf_new"); goto done; } - cprintf(regex, "^%s.*(.yang)$", module); - regexstr = cbuf_get(regex); + /* RFC 6020: The name of the file SHOULD be of the form: + module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' ) + revision-date ::= 4DIGIT "-" 2DIGIT "-" 2DIGIT + */ + cprintf(regex, "^%s(@[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])?(.yang)$", + module); if ((ndp = clicon_file_dirent(yang_dir, &dp, - regexstr, + cbuf_get(regex), S_IFREG)) < 0) goto done; /* Entries are sorted, last entry should be most recent date */ @@ -1436,8 +1440,8 @@ yang_parse_find_match(clicon_handle h, * @param module Name of main YANG module. More modules may be parsed if imported * @param revision Optional module revision date * @param ysp Yang specification. Should ave been created by caller using yspec_new - * @retval 0 Everything OK - * @retval -1 Error encountered + * @retval ymod Top-level yang (sub)module + * @retval NULL Error encountered * module-or-submodule-name ['@' revision-date] ( '.yang' / '.yin' ) * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree @@ -1457,7 +1461,7 @@ yang_parse2(clicon_handle h, FILE *f = NULL; cbuf *fbuf = NULL; char *filename; - yang_stmt *ys = NULL; + yang_stmt *ymod = NULL; struct stat st; int nr; @@ -1485,14 +1489,14 @@ yang_parse2(clicon_handle h, clicon_err(OE_UNIX, errno, "fopen(%s)", filename); goto done; } - if ((ys = yang_parse_file(h, f, filename, ysp)) == NULL) + if ((ymod = yang_parse_file(h, f, filename, ysp)) == NULL) goto done; done: if (fbuf) cbuf_free(fbuf); if (f) fclose(f); - return ys; + return ymod; /* top-level (sub)module */ } /*! Parse one yang module then go through (sub)modules and parse them recursively @@ -1501,9 +1505,9 @@ yang_parse2(clicon_handle h, * @param yang_dir Directory where all YANG module files reside * @param module Name of main YANG module. More modules may be parsed if imported * @param revision Optional module revision date - * @param ysp Yang specification. Should ave been created by caller using yspec_new - * @retval 0 Everything OK - * @retval -1 Error encountered + * @param ysp Yang specification. Should have been created by caller using yspec_new + * @retval ymod Top-level yang (sub)module + * @retval NULL Error encountered * Find a yang module file, and then recursively parse all its imported modules. * Calling order: * yang_parse # Parse top-level yang module. Expand and populate yang tree @@ -1521,15 +1525,15 @@ yang_parse1(clicon_handle h, yang_spec *ysp) { yang_stmt *yi = NULL; /* import */ - yang_stmt *ys; + yang_stmt *ymod; yang_stmt *yrev; char *modname; char *subrevision; - if ((ys = yang_parse2(h, yang_dir, module, revision, ysp)) == NULL) + if ((ymod = yang_parse2(h, yang_dir, module, revision, ysp)) == NULL) goto done; /* go through all import statements of ysp (or its module) */ - while ((yi = yn_each((yang_node*)ys, yi)) != NULL){ + while ((yi = yn_each((yang_node*)ymod, yi)) != NULL){ if (yi->ys_keyword != Y_IMPORT) continue; modname = yi->ys_argument; @@ -1539,12 +1543,12 @@ yang_parse1(clicon_handle h, subrevision = NULL; if (yang_find((yang_node*)ysp, Y_MODULE, modname) == NULL) if (yang_parse1(h, yang_dir, modname, subrevision, ysp) == NULL){ - ys = NULL; + ymod = NULL; goto done; } } done: - return ys; + return ymod; /* top-level (sub)module */ } /*! Parse top yang module including all its sub-modules. Expand and populate yang tree @@ -1574,22 +1578,21 @@ yang_parse(clicon_handle h, yang_spec *ysp) { int retval = -1; - yang_stmt *ys; + yang_stmt *ymod; /* Top-level yang (sub)module */ /* Step 1: parse from text to yang parse-tree. */ - if ((ys = yang_parse1(h, yang_dir, module, revision, ysp)) == NULL) + if ((ymod = yang_parse1(h, yang_dir, module, revision, ysp)) == NULL) goto done; /* Add top module name as dbspec-name */ - clicon_dbspec_name_set(h, ys->ys_argument); + clicon_dbspec_name_set(h, ymod->ys_argument); -#ifdef YANG_TYPE_CACHE /* Resolve all types */ - yang_apply((yang_node*)ys, ys_resolve_type, NULL); -#endif + yang_apply((yang_node*)ysp, ys_resolve_type, NULL); + /* Step 2: Macro expansion of all grouping/uses pairs. Expansion needs marking */ if (yang_expand((yang_node*)ysp) < 0) goto done; - yang_apply((yang_node*)ys, ys_flag_reset, (void*)YANG_FLAG_MARK); + yang_apply((yang_node*)ymod, ys_flag_reset, (void*)YANG_FLAG_MARK); /* Step 4: Go through parse tree and populate it with cv types */ if (yang_apply((yang_node*)ysp, ys_populate, NULL) < 0) goto done; @@ -1698,8 +1701,6 @@ yang_xpath_abs(yang_node *yn, int nvec; yang_node *ys = NULL; yang_stmt *ymod; - yang_spec *yspec; - yang_stmt *yimport; char *id; char *prefix = NULL; @@ -1736,15 +1737,8 @@ yang_xpath_abs(yang_node *yn, } prefix[id-vec[1]] = '\0'; id++; - if ((yimport = ys_module_import(ymod, prefix)) == NULL){ - clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix); + if ((ymod = yang_find_module_by_prefix((yang_stmt*)yn, prefix)) == NULL) goto done; - } - yspec = ys_spec(ymod); - if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL){ - clicon_err(OE_DB, 0, "Module referred to with prefix %s not found", prefix); - goto done; - } } ys = yang_xpath_vec((yang_node*)ymod, vec+1, nvec-1); done: diff --git a/lib/src/clixon_yang_parse.l b/lib/src/clixon_yang_parse.l index a19328b5..8364885a 100644 --- a/lib/src/clixon_yang_parse.l +++ b/lib/src/clixon_yang_parse.l @@ -179,7 +179,11 @@ clixon_yang_parsewrap(void) yang-version { BEGIN(ARGUMENT); return K_YANG_VERSION; } yin-element { BEGIN(ARGUMENT); return K_YIN_ELEMENT; } -. { return K_UNKNOWN; } +: { return *yytext; } +; { return *yytext; } +. { clixon_yang_parselval.string = strdup(yytext); + return CHAR;} + ; { BEGIN(KEYWORD); return *yytext; } \{ { BEGIN(KEYWORD); return *yytext; } diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y index 31c59afd..73b8dc12 100644 --- a/lib/src/clixon_yang_parse.y +++ b/lib/src/clixon_yang_parse.y @@ -239,7 +239,9 @@ ystack_push(struct clicon_yang_yacc_arg *yy, yang_node *yn) * Note: consumes 'argument' which assumes it is malloced and not freed by caller */ static yang_stmt * -ysp_add(struct clicon_yang_yacc_arg *yy, enum rfc_6020 keyword, char *argument) +ysp_add(struct clicon_yang_yacc_arg *yy, + enum rfc_6020 keyword, + char *argument) { struct ys_stack *ystack = yy->yy_stack; yang_stmt *ys = NULL; @@ -453,7 +455,8 @@ body_stmts : body_stmts body_stmt { clicon_debug(2,"body-stmts -> body-stmts | body_stmt { clicon_debug(2,"body-stmts -> body-stmt");} ; -body_stmt : feature_stmt { clicon_debug(2,"body-stmt -> feature-stmt");} +body_stmt : extension_stmt { clicon_debug(2,"body-stmt -> extension-stmt");} + | feature_stmt { clicon_debug(2,"body-stmt -> feature-stmt");} | identity_stmt { clicon_debug(2,"body-stmt -> identity-stmt");} | typedef_stmt { clicon_debug(2,"body-stmt -> typedef-stmt");} | grouping_stmt { clicon_debug(2,"body-stmt -> grouping-stmt");} @@ -467,6 +470,7 @@ data_def_stmt : container_stmt { clicon_debug(2,"data-def-stmt -> containe | leaf_list_stmt { clicon_debug(2,"data-def-stmt -> leaf-list-stmt");} | list_stmt { clicon_debug(2,"data-def-stmt -> list-stmt");} | choice_stmt { clicon_debug(2,"data-def-stmt -> choice-stmt");} + | anyxml_stmt { clicon_debug(2,"data-def-stmt -> anyxml-stmt");} | uses_stmt { clicon_debug(2,"data-def-stmt -> uses-stmt");} ; @@ -633,16 +637,17 @@ short_case_stmt : container_stmt { clicon_debug(2,"short-case-substmt -> conta | leaf_stmt { clicon_debug(2,"short-case-substmt -> leaf-stmt"); } | leaf_list_stmt { clicon_debug(2,"short-case-substmt -> leaf-list-stmt"); } | list_stmt { clicon_debug(2,"short-case-substmt -> list-stmt"); } + | anyxml_stmt { clicon_debug(2,"short-case-substmt -> anyxml-stmt");} ; /* case */ case_stmt : K_CASE id_arg_str ';' - { if (ysp_add(_yy, Y_CASE, $2) == NULL) _YYERROR("19"); + { if (ysp_add(_yy, Y_CASE, $2) == NULL) _YYERROR("22"); clicon_debug(2,"case-stmt -> CASE id-arg-str ;"); } | K_CASE id_arg_str - { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("20"); } + { if (ysp_add_push(_yy, Y_CASE, $2) == NULL) _YYERROR("23"); } '{' case_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("21"); + { if (ystack_pop(_yy) < 0) _YYERROR("24"); clicon_debug(2,"case-stmt -> CASE id-arg-str { case-substmts }"); } ; @@ -663,15 +668,42 @@ case_substmt : when_stmt { clicon_debug(2,"case-substmt -> when-stmt ; +/* anyxml */ +anyxml_stmt : K_ANYXML id_arg_str ';' + { if (ysp_add(_yy, Y_ANYXML, $2) == NULL) _YYERROR("25"); + clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str ;"); } + | K_ANYXML id_arg_str + { if (ysp_add_push(_yy, Y_ANYXML, $2) == NULL) _YYERROR("26"); } + '{' anyxml_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("27"); + clicon_debug(2,"anyxml-stmt -> ANYXML id-arg-str { anyxml-substmts }"); } + ; + +anyxml_substmts : anyxml_substmts anyxml_substmt + { clicon_debug(2,"anyxml-substmts -> anyxml-substmts anyxml-substmt"); } + | anyxml_substmt + { clicon_debug(2,"anyxml-substmts -> anyxml-substmt"); } + ; + +anyxml_substmt : when_stmt { clicon_debug(2,"anyxml-substmt -> when-stmt"); } + | if_feature_stmt { clicon_debug(2,"anyxml-substmt -> if-feature-stmt"); } + | must_stmt { clicon_debug(2,"anyxml-substmt -> must-stmt"); } + | config_stmt { clicon_debug(2,"anyxml-substmt -> config-stmt"); } + | mandatory_stmt { clicon_debug(2,"anyxml-substmt -> mandatory-stmt"); } + | status_stmt { clicon_debug(2,"anyxml-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"anyxml-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"anyxml-substmt -> reference-stmt"); } + | ustring ':' ustring ';' { clicon_debug(2,"anyxml-substmt -> anyxml extension"); } + ; /* uses */ uses_stmt : K_USES identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_USES, $2) == NULL) _YYERROR("19"); + { if (ysp_add(_yy, Y_USES, $2) == NULL) _YYERROR("28"); clicon_debug(2,"uses-stmt -> USES id-arg-str ;"); } | K_USES identifier_ref_arg_str - { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("20"); } + { if (ysp_add_push(_yy, Y_USES, $2) == NULL) _YYERROR("29"); } '{' uses_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("21"); + { if (ystack_pop(_yy) < 0) _YYERROR("30"); clicon_debug(2,"uses-stmt -> USES id-arg-str { uses-substmts }"); } ; @@ -694,12 +726,12 @@ uses_substmt : when_stmt { clicon_debug(2,"uses-substmt -> when-stmt /* refine XXX need further refining */ refine_stmt : K_REFINE id_arg_str ';' - { if (ysp_add(_yy, Y_REFINE, $2) == NULL) _YYERROR("21"); + { if (ysp_add(_yy, Y_REFINE, $2) == NULL) _YYERROR("31"); clicon_debug(2,"refine-stmt -> REFINE id-arg-str ;"); } | K_REFINE id_arg_str - { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("22"); } + { if (ysp_add_push(_yy, Y_REFINE, $2) == NULL) _YYERROR("32"); } '{' refine_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("23"); + { if (ystack_pop(_yy) < 0) _YYERROR("33"); clicon_debug(2,"refine-stmt -> REFINE id-arg-str { refine-substmts }"); } ; @@ -720,9 +752,9 @@ refine_substmt : must_stmt { clicon_debug(2,"refine-substmt -> must-stmt"); uses_augment_stmt : augment_stmt; augment_stmt : K_AUGMENT string - { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("22"); } + { if (ysp_add_push(_yy, Y_AUGMENT, $2) == NULL) _YYERROR("34"); } '{' augment_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("23"); + { if (ystack_pop(_yy) < 0) _YYERROR("35"); clicon_debug(2,"augment-stmt -> AUGMENT string { augment-substmts }"); } ; @@ -744,12 +776,12 @@ augment_substmt : when_stmt { clicon_debug(2,"augment-substmt -> when-s /* when */ when_stmt : K_WHEN string ';' - { if (ysp_add(_yy, Y_WHEN, $2) == NULL) _YYERROR("21"); + { if (ysp_add(_yy, Y_WHEN, $2) == NULL) _YYERROR("36"); clicon_debug(2,"when-stmt -> WHEN string ;"); } | K_WHEN string - { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("22"); } + { if (ysp_add_push(_yy, Y_WHEN, $2) == NULL) _YYERROR("37"); } '{' when_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("23"); + { if (ystack_pop(_yy) < 0) _YYERROR("38"); clicon_debug(2,"when-stmt -> WHEN string { when-substmts }"); } ; @@ -766,12 +798,12 @@ when_substmt : description_stmt { clicon_debug(2,"when-substmt -> description-s /* rpc */ rpc_stmt : K_RPC id_arg_str ';' - { if (ysp_add(_yy, Y_RPC, $2) == NULL) _YYERROR("21"); + { if (ysp_add(_yy, Y_RPC, $2) == NULL) _YYERROR("39"); clicon_debug(2,"rpc-stmt -> RPC id-arg-str ;"); } | K_RPC id_arg_str - { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("22"); } + { if (ysp_add_push(_yy, Y_RPC, $2) == NULL) _YYERROR("40"); } '{' rpc_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("23"); + { if (ystack_pop(_yy) < 0) _YYERROR("41"); clicon_debug(2,"rpc-stmt -> RPC id-arg-str { rpc-substmts }"); } ; @@ -794,9 +826,9 @@ rpc_substmt : if_feature_stmt { clicon_debug(2,"rpc-substmt -> if-feature-stm /* input */ input_stmt : K_INPUT - { if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("24"); } + { if (ysp_add_push(_yy, Y_INPUT, NULL) == NULL) _YYERROR("42"); } '{' input_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("25"); + { if (ystack_pop(_yy) < 0) _YYERROR("43"); clicon_debug(2,"input-stmt -> INPUT { input-substmts }"); } ; @@ -814,18 +846,18 @@ input_substmt : typedef_stmt { clicon_debug(2,"input-substmt -> typedef- /* output */ output_stmt : K_OUTPUT /* XXX reuse input-substatements since they are same */ - { if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("24"); } + { if (ysp_add_push(_yy, Y_OUTPUT, NULL) == NULL) _YYERROR("44"); } '{' input_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("25"); + { if (ystack_pop(_yy) < 0) _YYERROR("45"); clicon_debug(2,"output-stmt -> OUTPUT { input-substmts }"); } ; /* Typedef */ typedef_stmt : K_TYPEDEF id_arg_str - { if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("24"); } + { if (ysp_add_push(_yy, Y_TYPEDEF, $2) == NULL) _YYERROR("46"); } '{' typedef_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("25"); + { if (ystack_pop(_yy) < 0) _YYERROR("47"); clicon_debug(2,"typedef-stmt -> TYPEDEF id-arg-str { typedef-substmts }"); } ; @@ -847,13 +879,13 @@ typedef_substmt : type_stmt { clicon_debug(2,"typedef-substmt -> type-s /* Type */ type_stmt : K_TYPE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_TYPE, $2) == NULL) _YYERROR("26"); + { if (ysp_add(_yy, Y_TYPE, $2) == NULL) _YYERROR("48"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str ;");} | K_TYPE identifier_ref_arg_str - { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("27"); + { if (ysp_add_push(_yy, Y_TYPE, $2) == NULL) _YYERROR("49"); } '{' type_body_stmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("28"); + { if (ystack_pop(_yy) < 0) _YYERROR("50"); clicon_debug(2,"type-stmt -> TYPE identifier-ref-arg-str { type-body-stmts }");} ; @@ -892,9 +924,9 @@ type_body_stmt/* numerical-restrictions */ /* Grouping */ grouping_stmt : K_GROUPING id_arg_str - { if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("29"); } + { if (ysp_add_push(_yy, Y_GROUPING, $2) == NULL) _YYERROR("51"); } '{' grouping_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("30"); + { if (ystack_pop(_yy) < 0) _YYERROR("52"); clicon_debug(2,"grouping-stmt -> GROUPING id-arg-str { grouping-substmts }"); } ; @@ -915,13 +947,13 @@ grouping_substmt : status_stmt { clicon_debug(2,"grouping-substmt -> st /* length-stmt */ length_stmt : K_LENGTH string ';' /* XXX length-arg-str */ - { if (ysp_add(_yy, Y_LENGTH, $2) == NULL) _YYERROR("31"); + { if (ysp_add(_yy, Y_LENGTH, $2) == NULL) _YYERROR("53"); clicon_debug(2,"length-stmt -> LENGTH string ;"); } | K_LENGTH string - { if (ysp_add_push(_yy, Y_LENGTH, $2) == NULL) _YYERROR("32"); } + { if (ysp_add_push(_yy, Y_LENGTH, $2) == NULL) _YYERROR("54"); } '{' length_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("33"); + { if (ystack_pop(_yy) < 0) _YYERROR("55"); clicon_debug(2,"length-stmt -> LENGTH string { length-substmts }"); } ; @@ -940,13 +972,13 @@ length_substmt : error_message_stmt { clicon_debug(2,"length-substmt -> error-m /* Pattern */ pattern_stmt : K_PATTERN string ';' - { if (ysp_add(_yy, Y_PATTERN, $2) == NULL) _YYERROR("34"); + { if (ysp_add(_yy, Y_PATTERN, $2) == NULL) _YYERROR("56"); clicon_debug(2,"pattern-stmt -> PATTERN string ;"); } | K_PATTERN string - { if (ysp_add_push(_yy, Y_PATTERN, $2) == NULL) _YYERROR("35"); } + { if (ysp_add_push(_yy, Y_PATTERN, $2) == NULL) _YYERROR("57"); } '{' pattern_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("36"); + { if (ystack_pop(_yy) < 0) _YYERROR("58"); clicon_debug(2,"pattern-stmt -> PATTERN string { pattern-substmts }"); } ; @@ -962,18 +994,49 @@ pattern_substmt : reference_stmt { clicon_debug(2,"pattern-substmt -> refere | { clicon_debug(2,"pattern-substmt -> "); } ; -/* Feature */ -feature_stmt : K_FEATURE id_arg_str ';' - { if (ysp_add(_yy, Y_FEATURE, $2) == NULL) _YYERROR("50"); - clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } +/* Extension */ +extension_stmt: K_EXTENSION id_arg_str ';' + { if (ysp_add(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("59"); + clicon_debug(2,"extenstion-stmt -> EXTENSION id-arg-str ;"); } + | K_EXTENSION id_arg_str + { if (ysp_add_push(_yy, Y_EXTENSION, $2) == NULL) _YYERROR("60"); } + '{' extension_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("61"); + clicon_debug(2,"extension-stmt -> FEATURE id-arg-str { extension-substmts }"); } + ; - | K_FEATURE id_arg_str - { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("51"); } - '{' feature_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("52"); - clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); } + +/* extension substmts */ +extension_substmts : extension_substmts extension_substmt + { clicon_debug(2,"extension-substmts -> extension-substmts extension-substmt"); } + | extension_substmt + { clicon_debug(2,"extension-substmts -> extension-substmt"); } ; +extension_substmt : argument_stmt { clicon_debug(2,"extension-substmt -> argument-stmt"); } + | status_stmt { clicon_debug(2,"extension-substmt -> status-stmt"); } + | description_stmt { clicon_debug(2,"extension-substmt -> description-stmt"); } + | reference_stmt { clicon_debug(2,"extension-substmt -> reference-stmt"); } + | unknown_stmt { clicon_debug(2,"extension-substmt -> unknown-stmt");} + | { clicon_debug(2,"extension-substmt -> "); } + ; + +argument_stmt : K_ARGUMENT id_arg_str ';' + | K_ARGUMENT id_arg_str '{' '}' + ; + +/* Feature */ +feature_stmt : K_FEATURE id_arg_str ';' + { if (ysp_add(_yy, Y_FEATURE, $2) == NULL) _YYERROR("62"); + clicon_debug(2,"feature-stmt -> FEATURE id-arg-str ;"); } + | K_FEATURE id_arg_str + { if (ysp_add_push(_yy, Y_FEATURE, $2) == NULL) _YYERROR("63"); } + '{' feature_substmts '}' + { if (ystack_pop(_yy) < 0) _YYERROR("64"); + clicon_debug(2,"feature-stmt -> FEATURE id-arg-str { feature-substmts }"); } + ; + +/* feature substmts */ feature_substmts : feature_substmts feature_substmt { clicon_debug(2,"feature-substmts -> feature-substmts feature-substmt"); } | feature_substmt @@ -990,13 +1053,13 @@ feature_substmt : if_feature_stmt { clicon_debug(2,"feature-substmt -> if-fea /* Identity */ identity_stmt : K_IDENTITY string ';' /* XXX identifier-arg-str */ - { if (ysp_add(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("53"); + { if (ysp_add(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("65"); clicon_debug(2,"identity-stmt -> IDENTITY string ;"); } | K_IDENTITY string - { if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("54"); } + { if (ysp_add_push(_yy, Y_IDENTITY, $2) == NULL) _YYERROR("66"); } '{' identity_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("55"); + { if (ystack_pop(_yy) < 0) _YYERROR("67"); clicon_debug(2,"identity-stmt -> IDENTITY string { identity-substmts }"); } ; @@ -1016,13 +1079,13 @@ identity_substmt : base_stmt { clicon_debug(2,"identity-substmt -> base- /* range-stmt */ range_stmt : K_RANGE string ';' /* XXX range-arg-str */ - { if (ysp_add(_yy, Y_RANGE, $2) == NULL) _YYERROR("56"); + { if (ysp_add(_yy, Y_RANGE, $2) == NULL) _YYERROR("68"); clicon_debug(2,"range-stmt -> RANGE string ;"); } | K_RANGE string - { if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("57"); } + { if (ysp_add_push(_yy, Y_RANGE, $2) == NULL) _YYERROR("69"); } '{' range_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("58"); + { if (ystack_pop(_yy) < 0) _YYERROR("70"); clicon_debug(2,"range-stmt -> RANGE string { range-substmts }"); } ; @@ -1041,13 +1104,12 @@ range_substmt : error_message_stmt { clicon_debug(2,"range-substmt -> error-me /* enum-stmt */ enum_stmt : K_ENUM string ';' - { if (ysp_add(_yy, Y_ENUM, $2) == NULL) _YYERROR("59"); + { if (ysp_add(_yy, Y_ENUM, $2) == NULL) _YYERROR("71"); clicon_debug(2,"enum-stmt -> ENUM string ;"); } - | K_ENUM string - { if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("60"); } + { if (ysp_add_push(_yy, Y_ENUM, $2) == NULL) _YYERROR("72"); } '{' enum_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("61"); + { if (ystack_pop(_yy) < 0) _YYERROR("73"); clicon_debug(2,"enum-stmt -> ENUM string { enum-substmts }"); } ; @@ -1067,13 +1129,12 @@ enum_substmt : value_stmt { clicon_debug(2,"enum-substmt -> value-stm /* bit-stmt */ bit_stmt : K_BIT string ';' - { if (ysp_add(_yy, Y_BIT, $2) == NULL) _YYERROR("62"); + { if (ysp_add(_yy, Y_BIT, $2) == NULL) _YYERROR("74"); clicon_debug(2,"bit-stmt -> BIT string ;"); } - | K_BIT string - { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("63"); } + { if (ysp_add_push(_yy, Y_BIT, $2) == NULL) _YYERROR("75"); } '{' bit_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("64"); + { if (ystack_pop(_yy) < 0) _YYERROR("76"); clicon_debug(2,"bit-stmt -> BIT string { bit-substmts }"); } ; @@ -1092,13 +1153,13 @@ bit_substmt : position_stmt { clicon_debug(2,"bit-substmt -> positition /* mus-stmt */ must_stmt : K_MUST string ';' - { if (ysp_add(_yy, Y_MUST, $2) == NULL) _YYERROR("65"); + { if (ysp_add(_yy, Y_MUST, $2) == NULL) _YYERROR("77"); clicon_debug(2,"must-stmt -> MUST string ;"); } | K_MUST string - { if (ysp_add_push(_yy, Y_MUST, $2) == NULL) _YYERROR("66"); } + { if (ysp_add_push(_yy, Y_MUST, $2) == NULL) _YYERROR("78"); } '{' must_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("67"); + { if (ystack_pop(_yy) < 0) _YYERROR("79"); clicon_debug(2,"must-stmt -> MUST string { must-substmts }"); } ; @@ -1116,13 +1177,13 @@ must_substmt : error_message_stmt { clicon_debug(2,"must-substmt -> error-mes /* error-message-stmt */ error_message_stmt : K_ERROR_MESSAGE string ';' - { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2) == NULL) _YYERROR("68"); } + { if (ysp_add(_yy, Y_ERROR_MESSAGE, $2) == NULL) _YYERROR("80"); } /* import */ import_stmt : K_IMPORT id_arg_str - { if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("69"); } + { if (ysp_add_push(_yy, Y_IMPORT, $2) == NULL) _YYERROR("81"); } '{' import_substmts '}' - { if (ystack_pop(_yy) < 0) _YYERROR("70"); + { if (ystack_pop(_yy) < 0) _YYERROR("82"); clicon_debug(2,"import-stmt -> IMPORT id-arg-str { import-substmts }");} ; @@ -1139,144 +1200,144 @@ import_substmt : prefix_stmt { clicon_debug(2,"import-stmt -> prefix-stmt"); } /* Simple statements */ yang_version_stmt : K_YANG_VERSION string ';' /* XXX yang-version-arg-str */ - { if (ysp_add(_yy, Y_YANG_VERSION, $2) == NULL) _YYERROR("71"); + { if (ysp_add(_yy, Y_YANG_VERSION, $2) == NULL) _YYERROR("83"); clicon_debug(2,"yang-version-stmt -> YANG-VERSION string"); } ; fraction_digits_stmt : K_FRACTION_DIGITS string ';' /* XXX: fraction-digits-arg-str */ - { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2) == NULL) _YYERROR("72"); + { if (ysp_add(_yy, Y_FRACTION_DIGITS, $2) == NULL) _YYERROR("84"); clicon_debug(2,"fraction-digits-stmt -> FRACTION-DIGITS string"); } ; if_feature_stmt : K_IF_FEATURE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_IF_FEATURE, $2) == NULL) _YYERROR("73"); + { if (ysp_add(_yy, Y_IF_FEATURE, $2) == NULL) _YYERROR("85"); clicon_debug(2,"if-feature-stmt -> IF-FEATURE identifier-ref-arg-str"); } ; value_stmt : K_VALUE integer_value ';' - { if (ysp_add(_yy, Y_VALUE, $2) == NULL) _YYERROR("74"); + { if (ysp_add(_yy, Y_VALUE, $2) == NULL) _YYERROR("86"); clicon_debug(2,"value-stmt -> VALUE integer-value"); } ; position_stmt : K_POSITION integer_value ';' - { if (ysp_add(_yy, Y_POSITION, $2) == NULL) _YYERROR("75"); + { if (ysp_add(_yy, Y_POSITION, $2) == NULL) _YYERROR("87"); clicon_debug(2,"position-stmt -> POSITION integer-value"); } ; status_stmt : K_STATUS string ';' /* XXX: status-arg-str */ - { if (ysp_add(_yy, Y_STATUS, $2) == NULL) _YYERROR("76"); + { if (ysp_add(_yy, Y_STATUS, $2) == NULL) _YYERROR("88"); clicon_debug(2,"status-stmt -> STATUS string"); } ; config_stmt : K_CONFIG config_arg_str ';' - { if (ysp_add(_yy, Y_CONFIG, $2) == NULL) _YYERROR("77"); + { if (ysp_add(_yy, Y_CONFIG, $2) == NULL) _YYERROR("89"); clicon_debug(2,"config-stmt -> CONFIG config-arg-str"); } ; base_stmt : K_BASE identifier_ref_arg_str ';' - { if (ysp_add(_yy, Y_BASE, $2)== NULL) _YYERROR("78"); + { if (ysp_add(_yy, Y_BASE, $2)== NULL) _YYERROR("90"); clicon_debug(2,"base-stmt -> BASE identifier-ref-arg-str"); } ; path_stmt : K_PATH string ';' /* XXX: path-arg-str */ - { if (ysp_add(_yy, Y_PATH, $2)== NULL) _YYERROR("79"); + { if (ysp_add(_yy, Y_PATH, $2)== NULL) _YYERROR("91"); clicon_debug(2,"path-stmt -> PATH string"); } ; require_instance_stmt : K_REQUIRE_INSTANCE string ';' /* XXX: require-instance-arg-str */ - { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2)== NULL) _YYERROR("90"); + { if (ysp_add(_yy, Y_REQUIRE_INSTANCE, $2)== NULL) _YYERROR("92"); clicon_debug(2,"require-instance-stmt -> REQUIRE-INSTANCE string"); } ; units_stmt : K_UNITS string ';' - { if (ysp_add(_yy, Y_UNITS, $2)== NULL) _YYERROR("91"); + { if (ysp_add(_yy, Y_UNITS, $2)== NULL) _YYERROR("93"); clicon_debug(2,"units-stmt -> UNITS string"); } ; default_stmt : K_DEFAULT string ';' - { if (ysp_add(_yy, Y_DEFAULT, $2)== NULL) _YYERROR("92"); + { if (ysp_add(_yy, Y_DEFAULT, $2)== NULL) _YYERROR("94"); clicon_debug(2,"default-stmt -> DEFAULT string"); } ; contact_stmt : K_CONTACT string ';' - { if (ysp_add(_yy, Y_CONTACT, $2)== NULL) _YYERROR("93"); + { if (ysp_add(_yy, Y_CONTACT, $2)== NULL) _YYERROR("95"); clicon_debug(2,"contact-stmt -> CONTACT string"); } ; revision_date_stmt : K_REVISION_DATE string ';' /* XXX date-arg-str */ - { if (ysp_add(_yy, Y_REVISION_DATE, $2) == NULL) _YYERROR("94"); + { if (ysp_add(_yy, Y_REVISION_DATE, $2) == NULL) _YYERROR("96"); clicon_debug(2,"revision-date-stmt -> date;"); } ; include_stmt : K_INCLUDE id_arg_str ';' - { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("95"); + { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("97"); clicon_debug(2,"include-stmt -> id-arg-str"); } | K_INCLUDE id_arg_str '{' revision_date_stmt '}' - { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("96"); + { if (ysp_add(_yy, Y_INCLUDE, $2)== NULL) _YYERROR("98"); clicon_debug(2,"include-stmt -> id-arg-str { revision-date-stmt }"); } ; namespace_stmt : K_NAMESPACE string ';' /* XXX uri-str */ - { if (ysp_add(_yy, Y_NAMESPACE, $2)== NULL) _YYERROR("97"); + { if (ysp_add(_yy, Y_NAMESPACE, $2)== NULL) _YYERROR("99"); clicon_debug(2,"namespace-stmt -> NAMESPACE string"); } ; prefix_stmt : K_PREFIX string ';' /* XXX prefix-arg-str */ - { if (ysp_add(_yy, Y_PREFIX, $2)== NULL) _YYERROR("98"); + { if (ysp_add(_yy, Y_PREFIX, $2)== NULL) _YYERROR("100"); clicon_debug(2,"prefix-stmt -> PREFIX string ;");} ; description_stmt: K_DESCRIPTION string ';' - { if (ysp_add(_yy, Y_DESCRIPTION, $2)== NULL) _YYERROR("99"); + { if (ysp_add(_yy, Y_DESCRIPTION, $2)== NULL) _YYERROR("101"); clicon_debug(2,"description-stmt -> DESCRIPTION string ;");} ; organization_stmt: K_ORGANIZATION string ';' - { if (ysp_add(_yy, Y_ORGANIZATION, $2)== NULL) _YYERROR("100"); + { if (ysp_add(_yy, Y_ORGANIZATION, $2)== NULL) _YYERROR("102"); clicon_debug(2,"organization-stmt -> ORGANIZATION string ;");} ; min_elements_stmt: K_MIN_ELEMENTS integer_value ';' - { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2)== NULL) _YYERROR("101"); + { if (ysp_add(_yy, Y_MIN_ELEMENTS, $2)== NULL) _YYERROR("103"); clicon_debug(2,"min-elements-stmt -> MIN-ELEMENTS integer ;");} ; max_elements_stmt: K_MAX_ELEMENTS integer_value ';' - { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2)== NULL) _YYERROR("101"); + { if (ysp_add(_yy, Y_MAX_ELEMENTS, $2)== NULL) _YYERROR("104"); clicon_debug(2,"max-elements-stmt -> MIN-ELEMENTS integer ;");} ; reference_stmt: K_REFERENCE string ';' - { if (ysp_add(_yy, Y_REFERENCE, $2)== NULL) _YYERROR("101"); + { if (ysp_add(_yy, Y_REFERENCE, $2)== NULL) _YYERROR("105"); clicon_debug(2,"reference-stmt -> REFERENCE string ;");} ; mandatory_stmt: K_MANDATORY string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_MANDATORY, $2))== NULL) _YYERROR("102"); + if ((ys = ysp_add(_yy, Y_MANDATORY, $2))== NULL) _YYERROR("106"); clicon_debug(2,"mandatory-stmt -> MANDATORY mandatory-arg-str ;");} ; presence_stmt: K_PRESENCE string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_PRESENCE, $2))== NULL) _YYERROR("102"); + if ((ys = ysp_add(_yy, Y_PRESENCE, $2))== NULL) _YYERROR("107"); clicon_debug(2,"presence-stmt -> PRESENCE string ;");} ; ordered_by_stmt: K_ORDERED_BY string ';' { yang_stmt *ys; - if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2))== NULL) _YYERROR("102"); + if ((ys = ysp_add(_yy, Y_ORDERED_BY, $2))== NULL) _YYERROR("108"); clicon_debug(2,"ordered-by-stmt -> ORDERED-BY ordered-by-arg ;");} ; key_stmt : K_KEY id_arg_str ';' /* XXX key_arg_str */ - { if (ysp_add(_yy, Y_KEY, $2)== NULL) _YYERROR("103"); + { if (ysp_add(_yy, Y_KEY, $2)== NULL) _YYERROR("109"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; unique_stmt : K_UNIQUE id_arg_str ';' /* XXX key_arg_str */ - { if (ysp_add(_yy, Y_UNIQUE, $2)== NULL) _YYERROR("104"); + { if (ysp_add(_yy, Y_UNIQUE, $2)== NULL) _YYERROR("110"); clicon_debug(2,"key-stmt -> KEY id-arg-str ;");} ; @@ -1287,10 +1348,10 @@ integer_value : string { $$=$1; } ; identifier_ref_arg_str : string - { if (($$=prefix_id_join(NULL, $1)) == NULL) _YYERROR("105"); + { if (($$=prefix_id_join(NULL, $1)) == NULL) _YYERROR("111"); clicon_debug(2,"identifier-ref-arg-str -> string"); } | string ':' string - { if (($$=prefix_id_join($1, $3)) == NULL) _YYERROR("106"); + { if (($$=prefix_id_join($1, $3)) == NULL) _YYERROR("112"); clicon_debug(2,"identifier-ref-arg-str -> prefix : string"); } ; diff --git a/lib/src/clixon_yang_type.c b/lib/src/clixon_yang_type.c index 6533ff7d..8fc59ed0 100644 --- a/lib/src/clixon_yang_type.c +++ b/lib/src/clixon_yang_type.c @@ -71,19 +71,10 @@ /* * Local types and variables */ -/* Struct used to map between int and strings. Used for: - * - mapping yang types/typedefs (strings) and cligen types (ints). - * - mapping yang keywords (strings) and enum (clicon) - * (same struct in clicon_yang.c) - */ -struct map_str2int{ - char *ms_str; /* string as in 4.2.4 in RFC 6020 */ - int ms_int; -}; /* Mapping between yang types <--> cligen types Note, first match used wne translating from cv to yang --> order is significant */ -static const struct map_str2int ytmap[] = { +static const map_str2int ytmap[] = { {"int32", CGV_INT32}, /* NOTE, first match on right is significant, dont move */ {"string", CGV_STRING}, /* NOTE, first match on right is significant, dont move */ {"string", CGV_REST}, /* For cv -> yang translation of rest */ @@ -105,9 +96,24 @@ static const struct map_str2int ytmap[] = { {"uint32", CGV_UINT32}, {"uint64", CGV_UINT64}, {"union", CGV_REST}, /* Is replaced by actual type */ - {NULL, -1} + {NULL, -1} }; +/* return 1 if built-in, 0 if not */ +static int +yang_builtin(char *type) +{ + if (clicon_str2int(ytmap, type) != -1) + return 1; +#if 0 + const struct map_str2int *yt; + for (yt = &ytmap[0]; yt->ms_str; yt++) + if (strcmp(yt->ms_str, type) == 0) + return 1; +#endif + return 0; +} + int yang_type_cache_set(yang_type_cache **ycache0, yang_stmt *resolved, @@ -174,7 +180,8 @@ yang_type_cache_get(yang_type_cache *ycache, } int -yang_type_cache_cp(yang_type_cache **ycnew, yang_type_cache *ycold) +yang_type_cache_cp(yang_type_cache **ycnew, + yang_type_cache *ycold) { int retval = -1; int options; @@ -207,7 +214,8 @@ yang_type_cache_free(yang_type_cache *ycache) /*! Resolve types: populate type caches */ int -ys_resolve_type(yang_stmt *ys, void *arg) +ys_resolve_type(yang_stmt *ys, + void *arg) { int retval = -1; int options = 0x0; @@ -219,8 +227,9 @@ ys_resolve_type(yang_stmt *ys, void *arg) if (ys->ys_keyword != Y_TYPE) return 0; - yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, - &options, &mincv, &maxcv, &pattern, &fraction); + if (yang_type_resolve((yang_stmt*)ys->ys_parent, ys, &resolved, + &options, &mincv, &maxcv, &pattern, &fraction) < 0) + goto done; if (yang_type_cache_set(&ys->ys_typecache, resolved, options, mincv, maxcv, pattern, fraction) < 0) goto done; @@ -229,20 +238,6 @@ ys_resolve_type(yang_stmt *ys, void *arg) return retval; } - -/* return 1 if built-in, 0 if not */ -static int -yang_builtin(char *type) -{ - const struct map_str2int *yt; - - /* built-in types */ - for (yt = &ytmap[0]; yt->ms_str; yt++) - if (strcmp(yt->ms_str, type) == 0) - return 1; - return 0; -} - /*! Translate from a yang type to a cligen variable type * * Currently many built-in types from RFC6020 and some RFC6991 types. @@ -252,17 +247,17 @@ yang_builtin(char *type) * Return 0 if no match but set cv_type to CGV_ERR */ int -yang2cv_type(char *ytype, enum cv_type *cv_type) +yang2cv_type(char *ytype, + enum cv_type *cv_type) { - const struct map_str2int *yt; + int ret; *cv_type = CGV_ERR; /* built-in types */ - for (yt = &ytmap[0]; yt->ms_str; yt++) - if (strcmp(yt->ms_str, ytype) == 0){ - *cv_type = yt->ms_int; - return 0; - } + if ((ret = clicon_str2int(ytmap, ytype)) != -1){ + *cv_type = ret; + return 0; + } /* special derived types */ if (strcmp("ipv4-address", ytype) == 0){ /* RFC6991 */ *cv_type = CGV_IPV4ADDR; @@ -300,14 +295,13 @@ yang2cv_type(char *ytype, enum cv_type *cv_type) char * cv2yang_type(enum cv_type cv_type) { - const struct map_str2int *yt; char *ytype; + const char *str; ytype = "empty"; /* built-in types */ - for (yt = &ytmap[0]; yt->ms_str; yt++) - if (yt->ms_int == cv_type) - return yt->ms_str; + if ((str = clicon_int2str(ytmap, cv_type)) != NULL) + return (char*)str; /* special derived types */ if (cv_type == CGV_IPV4ADDR) /* RFC6991 */ @@ -343,7 +337,9 @@ cv2yang_type(enum cv_type cv_type) * @param[out] cvtype */ int -clicon_type2cv(char *origtype, char *restype, enum cv_type *cvtype) +clicon_type2cv(char *origtype, + char *restype, + enum cv_type *cvtype) { int retval = -1; @@ -710,7 +706,7 @@ ys_typedef_up(yang_stmt *ys) This is a sanity check of base identity of identity-ref and for identity statements. -Return true if node is identityref and is derived from identity_name + Return true if node is identityref and is derived from identity_name The derived-from() function returns true if the (first) node (in document order in the argument "nodes") is a node of type identityref, and its value is an identity that is derived from the identity @@ -728,16 +724,14 @@ Return true if node is identityref and is derived from identity_name Så vad är det denna function ska göra? Svar: 1 */ yang_stmt * -yang_find_identity(yang_stmt *ys, char *identity) +yang_find_identity(yang_stmt *ys, + char *identity) { char *id; char *prefix = NULL; - yang_stmt *yimport; - yang_spec *yspec; yang_stmt *ymodule; yang_stmt *yid = NULL; yang_node *yn; - yang_stmt *ymod; if ((id = strchr(identity, ':')) == NULL) id = identity; @@ -748,12 +742,8 @@ yang_find_identity(yang_stmt *ys, char *identity) } /* No, now check if identityref is derived from base */ if (prefix){ /* Go to top and find import that matches */ - ymod = ys_module(ys); - if ((yimport = ys_module_import(ymod, prefix)) == NULL) + if ((ymodule = yang_find_module_by_prefix(ys, prefix)) == NULL) goto done; - yspec = ys_spec(ys); - if ((ymodule = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL) - goto done; /* unresolved */ yid = yang_find((yang_node*)ymodule, Y_IDENTITY, id); } else{ @@ -845,12 +835,10 @@ yang_type_resolve(yang_stmt *ys, yang_stmt *ylength; yang_stmt *ypattern; yang_stmt *yfraction; - yang_stmt *yimport; char *type; char *prefix = NULL; int retval = -1; yang_node *yn; - yang_spec *yspec; yang_stmt *ymod; if (options) @@ -879,14 +867,8 @@ yang_type_resolve(yang_stmt *ys, /* Not basic type. Now check if prefix which means we look in other module */ if (prefix){ /* Go to top and find import that matches */ - ymod = ys_module(ys); - if ((yimport = ys_module_import(ymod, prefix)) == NULL){ - clicon_err(OE_DB, 0, "Prefix %s not defined not found", prefix); + if ((ymod = yang_find_module_by_prefix(ys, prefix)) == NULL) goto done; - } - yspec = ys_spec(ys); - if ((ymod = yang_find((yang_node*)yspec, Y_MODULE, yimport->ys_argument)) == NULL) - goto ok; /* unresolved */ if ((rytypedef = yang_find((yang_node*)ymod, Y_TYPEDEF, type)) == NULL) goto ok; /* unresolved */ } diff --git a/test/lib.sh b/test/lib.sh index 423e0c22..074fb965 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -42,7 +42,7 @@ expectfn(){ # echo "expect:\"$expect\"" # echo "match:\"$match\"" if [ -z "$match" ]; then - err $expect "$ret" + err "$expect" "$ret" fi if [ -n "$expect2" ]; then match=`echo "$ret" | grep -EZo "$expect2"` diff --git a/test/test2.sh b/test/test2.sh index 8099f11f..9af012db 100755 --- a/test/test2.sh +++ b/test/test2.sh @@ -86,6 +86,12 @@ expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" +new "netconf edit state operation should fail" +expecteof "$clixon_netconf -qf $clixon_cf" "eth1eth]]>]]>" "^invalid-value" + +new "netconf get state operation" +expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>" "^]]>]]>$" + new "netconf lock/unlock" expecteof "$clixon_netconf -qf $clixon_cf" "]]>]]>]]>]]>" "^]]>]]>]]>]]>$" diff --git a/test/test4.sh b/test/test4.sh index fb21a3bf..4ce09d69 100755 --- a/test/test4.sh +++ b/test/test4.sh @@ -7,6 +7,7 @@ # For memcheck # clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" clixon_netconf=clixon_netconf +clixon_cli=clixon_cli cat < /tmp/test.yang module ietf-ip{ @@ -40,6 +41,12 @@ module ietf-ip{ } } } + container state { + config false; + leaf-list op { + type string; + } + } } EOF @@ -74,6 +81,19 @@ expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^hejhopp]]>]]>$" +new "netconf get (state data XXX should be some)" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "]]>]]>" "^125]]>]]>$" + +new "cli set leaf-list" +expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test set x f e foo" "" + +new "cli show leaf-list" +expectfn "$clixon_cli -1f $clixon_cf -y /tmp/test show xpath /x/f/e" "foo" + +new "netconf set state data (not allowed)" +expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/test" "42]]>]]>" "^invalid-value" + + new "Kill backend" # Check if still alive pid=`pgrep clixon_backend` diff --git a/test/test5.sh b/test/test5.sh index 148ed1ab..acf6af59 100755 --- a/test/test5.sh +++ b/test/test5.sh @@ -1,12 +1,12 @@ #!/bin/bash -# Test5: datastore +# Test5: datastore tests. +# Just run a binary direct to datastore. No clixon. # include err() and new() functions . ./lib.sh datastore=datastore_client - cat < /tmp/ietf-ip.yang module ietf-ip{ container x { @@ -54,7 +54,7 @@ run(){ rm -rf $dir/* conf="-d candidate -b $dir -p ../datastore/$name/$name.so -y /tmp -m ietf-ip" -# echo "conf:$conf" + echo "conf:$conf" new "datastore $name init" expectfn "$datastore $conf init" "" @@ -139,8 +139,18 @@ run(){ new "datastore $name create leaf" expectfn "$datastore $conf put create 13newentry" + new "datastore other db init" + expectfn "$datastore -d kalle -b $dir -p ../datastore/$name/$name.so -y /tmp -m ietf-ip init" + + new "datastore other db copy" + expectfn "$datastore $conf copy kalle" "" + + diff $dir/kalle_db $dir/candidate_db + + new "datastore lock" + expectfn "$datastore $conf lock 756" "" + #leaf-list - rm -rf $dir } diff --git a/test/test6.sh b/test/test6.sh new file mode 100755 index 00000000..e180e7b9 --- /dev/null +++ b/test/test6.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Test6: Yang specifics: rpc and state info + +# include err() and new() functions +. ./lib.sh + +# For memcheck +# clixon_netconf="valgrind --leak-check=full --show-leak-kinds=all clixon_netconf" +clixon_netconf=clixon_netconf +clixon_cli=clixon_cli + +cat < /tmp/rpc.yang +module ietf-ip{ + rpc fib-route { + input { + leaf name { + type string; + mandatory "true"; + } + leaf destination-address { + type string; + } + } + output { + container route { + leaf address{ + type string; + } + leaf address{ + type string; + } + } + } + } +} +EOF + +# kill old backend (if any) +new "kill old backend" +sudo clixon_backend -zf $clixon_cf -y /tmp/rpc +if [ $? -ne 0 ]; then + err +fi + +new "start backend" +# start new backend +sudo clixon_backend -If $clixon_cf -y /tmp/rpc +if [ $? -ne 0 ]; then + err +fi +new "netconf rpc (notyet)" +#expecteof "$clixon_netconf -qf $clixon_cf -y /tmp/rpc" "]]>]]>" "^]]>]]>$" + +new "Kill backend" +# Check if still alive +pid=`pgrep clixon_backend` +if [ -z "$pid" ]; then + err "backend already dead" +fi +# kill backend +sudo clixon_backend -zf $clixon_cf +if [ $? -ne 0 ]; then + err "kill backend" +fi diff --git a/yang/clixon-config@2017-07-02.yang b/yang/clixon-config@2017-07-02.yang new file mode 100644 index 00000000..ed704545 --- /dev/null +++ b/yang/clixon-config@2017-07-02.yang @@ -0,0 +1,179 @@ + module clixon-config { + + prefix cc; + + organization + "Clicon / Clixon"; + + contact + "Olof Hagsand "; + + description + "Clixon configuration file + ***** BEGIN LICENSE BLOCK ***** + Copyright (C) 2009-2017 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 *****"; + + revision 2017-07-02 { + description + "Initial revision"; + } + leaf CLICON_CONFIGFILE{ + type string; + default "sysconfdir/$APPNAME.conf"; + description "Location of configuration-file for default values (this file)"; + } + leaf CLICON_YANG_DIR { + type string; + default "prefix/share/$APPNAME/yang"; + description "Location of YANG module and submodule files. Only if CLICON_DBSPEC_TYPE is YANG"; + } + leaf CLICON_YANG_MODULE_MAIN { + type string; + default "clicon"; + description "Option used to construct initial yang file: + [@] + This option is only relevant if CLICON_DBSPEC_TYPE is YANG"; + } + leaf CLICON_YANG_MODULE_REVISION { + type string; + description "Option used to construct initial yang file: + [@]"; + } + leaf CLICON_BACKEND_DIR { + type string; + default "libdir/$APPNAME/backend"; + description "Location of backend .so plugins"; + } + leaf CLICON_NETCONF_DIR { + type string; + default "libdir/$APPNAME/netconf"; + description "Location of netconf (frontend) .so plugins"; + } + leaf CLICON_RESTCONF_DIR { + type string; + default "libdir/$APPNAME/restconf"; + description "Location of restconf (frontend) .so plugins"; + } + leaf CLICON_CLI_DIR { + type string; + default "libdir/$APPNAME/cli"; + description "Location of cli frontend .so plugins"; + } + leaf CLICON_CLISPEC_DIR { + type string; + default "libdir/$APPNAME/clispec"; + description "Location of frontend .cli cligen spec files"; + } + leaf CLICON_USE_STARTUP_CONFIG { + type int32; + default 0; + description "Enabled uses \"startup\" configuration on boot"; + } + leaf CLICON_SOCK_FAMILY { + type string; + default "UNIX"; + description "Address family for communicating with clixon_backend (UNIX|IPv4|IPv6)"; + } + leaf CLICON_SOCK { + type string; + default "localstatedir/$APPNAME/$APPNAME.sock"; + description "If family above is AF_UNIX: Unix socket for communicating with + clixon_backend. If family above is AF_INET: IPv4 address"; + } + leaf CLICON_SOCK_PORT { + type int32; + default 4535; + description "Inet socket port for communicating with clixon_backend (only IPv4|IPv6)"; + } + leaf CLICON_BACKEND_PIDFILE { + type string; + default "localstatedir/$APPNAME/$APPNAME.pidfile"; + description "Process-id file"; + } + leaf CLICON_SOCK_GROUP { + type string; + default "clicon"; + description "Group membership to access clixon_backend unix socket"; + } + leaf CLICON_AUTOCOMMIT { + type int32; + default 0; + description "Set if all configuration changes are committed directly, + commit command unnecessary"; + } + leaf CLICON_MASTER_PLUGIN { + type string; + default "master"; + description "Name of master plugin (both frontend and backend). + Master plugin has special callbacks for frontends. + See clicon user manual for more info."; + } + leaf CLICON_CLI_MODE { + type string; + default "base"; + description "Startup CLI mode. This should match the CLICON_MODE in your startup clispec file"; + } + leaf CLICON_CLI_GENMODEL { + type int32; + default 1; + description "Generate code for CLI completion of existing db symbols. + Add name=\"myspec\" in datamodel spec and reference as @myspec"; + } + leaf CLICON_CLI_GENMODEL_COMPLETION { + type int32; + default 0; + description "Generate code for CLI completion of existing db symbols"; + } + leaf CLICON_CLI_GENMODEL_TYPE { + type string; + default "VARS"; + description "How to generate and show CLI syntax: VARS|ALL"; + } + leaf CLICON_XMLDB_DIR { + type string; + default "localstatedir/$APPNAME"; + description "Directory where \"running\", \"candidate\" and \"startup\" are placed"; + } + leaf CLICON_XMLDB_PLUGIN { + type string; + default "libdir/xmldb/text.so"; + description "XMLDB datastore plugin filename (see datastore/ and clixon_xml_db.[ch])"; + } + leaf CLICON_CLI_VARONLY { + type int32; + default 1; + description "Dont include keys in cvec in cli vars callbacks, ie a & k in 'a k ' ignored"; + } + + leaf CLICON_RESTCONF_PATH { + type string; + default "/www-data/fastcgi_restconf.sock"; + description "FastCGI unix socket. Should be specified in webserver + Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock;"; + } +} diff --git a/yang/ietf-netconf@2011-06-01.yang b/yang/ietf-netconf@2011-06-01.yang new file mode 100644 index 00000000..9fac6c58 --- /dev/null +++ b/yang/ietf-netconf@2011-06-01.yang @@ -0,0 +1,926 @@ + module ietf-netconf { + + // the namespace for NETCONF XML definitions is unchanged + // from RFC 4741, which this document replaces + namespace "urn:ietf:params:xml:ns:netconf:base:1.0"; + + prefix nc; + + import ietf-inet-types { + prefix inet; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + WG Chair: Bert Wijnen + + + WG Chair: Mehmet Ersue + + + Editor: Martin Bjorklund + + + Editor: Juergen Schoenwaelder + + + Editor: Andy Bierman + "; + + description + "NETCONF Protocol Data Types and Protocol Operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6241; see + the RFC itself for full legal notices."; + revision 2011-06-01 { + description + "Initial revision"; + reference + "RFC 6241: Network Configuration Protocol"; + } + + extension get-filter-element-attributes { + description + "If this extension is present within an 'anyxml' + statement named 'filter', which must be conceptually + defined within the RPC input section for the + and protocol operations, then the + following unqualified XML attribute is supported + within the element, within a or + protocol operation: + + type : optional attribute with allowed + value strings 'subtree' and 'xpath'. + If missing, the default value is 'subtree'. + + If the 'xpath' feature is supported, then the + following unqualified XML attribute is + also supported: + + select: optional attribute containing a + string representing an XPath expression. + The 'type' attribute must be equal to 'xpath' + if this attribute is present."; + } + + // NETCONF capabilities defined as features + feature writable-running { + description + "NETCONF :writable-running capability; + If the server advertises the :writable-running + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.2"; + } + + feature candidate { + description + "NETCONF :candidate capability; + If the server advertises the :candidate + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.3"; + } + + feature confirmed-commit { + if-feature candidate; + description + "NETCONF :confirmed-commit:1.1 capability; + If the server advertises the :confirmed-commit:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + + reference "RFC 6241, Section 8.4"; + } + + feature rollback-on-error { + description + "NETCONF :rollback-on-error capability; + If the server advertises the :rollback-on-error + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.5"; + } + + feature validate { + description + "NETCONF :validate:1.1 capability; + If the server advertises the :validate:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.6"; + } + + feature startup { + description + "NETCONF :startup capability; + If the server advertises the :startup + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.7"; + } + + feature url { + description + "NETCONF :url capability; + If the server advertises the :url + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.8"; + } + + feature xpath { + description + "NETCONF :xpath capability; + If the server advertises the :xpath + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.9"; + } + + // NETCONF Simple Types + + typedef session-id-type { + type uint32 { + range "1..max"; + } + description + "NETCONF Session Id"; + } + + typedef session-id-or-zero-type { + type uint32; + description + "NETCONF Session Id or Zero to indicate none"; + } + typedef error-tag-type { + type enumeration { + enum in-use { + description + "The request requires a resource that + already is in use."; + } + enum invalid-value { + description + "The request specifies an unacceptable value for one + or more parameters."; + } + enum too-big { + description + "The request or response (that would be generated) is + too large for the implementation to handle."; + } + enum missing-attribute { + description + "An expected attribute is missing."; + } + enum bad-attribute { + description + "An attribute value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-attribute { + description + "An unexpected attribute is present."; + } + enum missing-element { + description + "An expected element is missing."; + } + enum bad-element { + description + "An element value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-element { + description + "An unexpected element is present."; + } + enum unknown-namespace { + description + "An unexpected namespace is present."; + } + enum access-denied { + description + "Access to the requested protocol operation or + data model is denied because authorization failed."; + } + enum lock-denied { + description + "Access to the requested lock is denied because the + lock is currently held by another entity."; + } + enum resource-denied { + description + "Request could not be completed because of + insufficient resources."; + } + enum rollback-failed { + description + "Request to roll back some configuration change (via + rollback-on-error or operations) + was not completed for some reason."; + + } + enum data-exists { + description + "Request could not be completed because the relevant + data model content already exists. For example, + a 'create' operation was attempted on data that + already exists."; + } + enum data-missing { + description + "Request could not be completed because the relevant + data model content does not exist. For example, + a 'delete' operation was attempted on + data that does not exist."; + } + enum operation-not-supported { + description + "Request could not be completed because the requested + operation is not supported by this implementation."; + } + enum operation-failed { + description + "Request could not be completed because the requested + operation failed for some reason not covered by + any other error condition."; + } + enum partial-operation { + description + "This error-tag is obsolete, and SHOULD NOT be sent + by servers conforming to this document."; + } + enum malformed-message { + description + "A message could not be handled because it failed to + be parsed correctly. For example, the message is not + well-formed XML or it uses an invalid character set."; + } + } + description "NETCONF Error Tag"; + reference "RFC 6241, Appendix A"; + } + + typedef error-severity-type { + type enumeration { + enum error { + description "Error severity"; + } + enum warning { + description "Warning severity"; + } + } + description "NETCONF Error Severity"; + reference "RFC 6241, Section 4.3"; + } + + typedef edit-operation-type { + type enumeration { + enum merge { + description + "The configuration data identified by the + element containing this attribute is merged + with the configuration at the corresponding + level in the configuration datastore identified + by the target parameter."; + } + enum replace { + description + "The configuration data identified by the element + containing this attribute replaces any related + configuration in the configuration datastore + identified by the target parameter. If no such + configuration data exists in the configuration + datastore, it is created. Unlike a + operation, which replaces the + entire target configuration, only the configuration + actually present in the config parameter is affected."; + } + enum create { + description + "The configuration data identified by the element + containing this attribute is added to the + configuration if and only if the configuration + data does not already exist in the configuration + datastore. If the configuration data exists, an + element is returned with an + value of 'data-exists'."; + } + enum delete { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if and only if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, an element is returned with + an value of 'data-missing'."; + } + enum remove { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, the 'remove' operation is silently ignored + by the server."; + } + } + default "merge"; + description "NETCONF 'operation' attribute values"; + reference "RFC 6241, Section 7.2"; + } + + // NETCONF Standard Protocol Operations + + rpc get-config { + description + "Retrieve all or part of a specified configuration."; + + reference "RFC 6241, Section 7.1"; + + input { + container source { + description + "Particular configuration to retrieve."; + + choice config-source { + mandatory true; + description + "The configuration to retrieve."; + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source. + This is optional-to-implement on the server because + not all servers will support filtering for this + datastore."; + } + } + } + + anyxml filter { + description + "Subtree or XPath filter to use."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the source datastore subset that matched + the filter criteria (if any). An empty data container + indicates that the request did not produce any results."; + } + } + } + + rpc edit-config { + description + "The operation loads all or part of a specified + configuration to the specified target configuration."; + + reference "RFC 6241, Section 7.2"; + + input { + container target { + description + "Particular configuration to edit."; + + choice config-target { + mandatory true; + description + "The configuration target."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config source."; + } + } + } + + leaf default-operation { + type enumeration { + enum merge { + description + "The default operation is merge."; + } + enum replace { + description + "The default operation is replace."; + } + enum none { + description + "There is no default operation."; + } + } + default "merge"; + description + "The default operation to use."; + } + + leaf test-option { + if-feature validate; + type enumeration { + enum test-then-set { + description + "The server will test and then set if no errors."; + } + enum set { + description + "The server will set without a test first."; + } + + enum test-only { + description + "The server will only test and not set, even + if there are no errors."; + } + } + default "test-then-set"; + description + "The test option to use."; + } + + leaf error-option { + type enumeration { + enum stop-on-error { + description + "The server will stop on errors."; + } + enum continue-on-error { + description + "The server may continue on errors."; + } + enum rollback-on-error { + description + "The server will roll back on errors. + This value can only be used if the 'rollback-on-error' + feature is supported."; + } + } + default "stop-on-error"; + description + "The error option to use."; + } + + choice edit-content { + mandatory true; + description + "The content for the edit operation."; + + anyxml config { + description + "Inline Config content."; + } + leaf url { + if-feature url; + type inet:uri; + description + "URL-based config content."; + } + } + } + } + + rpc copy-config { + description + "Create or replace an entire configuration datastore with the + contents of another complete configuration datastore."; + + reference "RFC 6241, Section 7.3"; + + input { + container target { + description + "Particular configuration to copy to."; + + choice config-target { + mandatory true; + description + "The configuration target of the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config target. + This is optional-to-implement on the server."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + + container source { + description + "Particular configuration to copy from."; + + choice config-source { + mandatory true; + description + "The configuration source for the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + + rpc delete-config { + description + "Delete a configuration datastore."; + + reference "RFC 6241, Section 7.4"; + + input { + container target { + description + "Particular configuration to delete."; + + choice config-target { + mandatory true; + description + "The configuration target to delete."; + + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + } + } + + rpc lock { + description + "The lock operation allows the client to lock the configuration + system of a device."; + reference "RFC 6241, Section 7.5"; + + input { + container target { + description + "Particular configuration to lock."; + + choice config-target { + mandatory true; + description + "The configuration target to lock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc unlock { + description + "The unlock operation is used to release a configuration lock, + previously obtained with the 'lock' operation."; + + reference "RFC 6241, Section 7.6"; + + input { + container target { + description + "Particular configuration to unlock."; + + choice config-target { + mandatory true; + description + "The configuration target to unlock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc get { + description + "Retrieve running configuration and device state information."; + + reference "RFC 6241, Section 7.7"; + + input { + anyxml filter { + description + "This parameter specifies the portion of the system + configuration and state data to retrieve."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the running datastore subset and/or state + data that matched the filter criteria (if any). + An empty data container indicates that the request did not + produce any results."; + } + } + } + + rpc close-session { + description + "Request graceful termination of a NETCONF session."; + + reference "RFC 6241, Section 7.8"; + } + + rpc kill-session { + description + "Force the termination of a NETCONF session."; + + reference "RFC 6241, Section 7.9"; + + input { + leaf session-id { + type session-id-type; + mandatory true; + description + "Particular session to kill."; + } + } + } + + rpc commit { + if-feature candidate; + + description + "Commit the candidate configuration as the device's new + current configuration."; + + reference "RFC 6241, Section 8.3.4.1"; + + input { + leaf confirmed { + if-feature confirmed-commit; + type empty; + description + "Requests a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf confirm-timeout { + if-feature confirmed-commit; + type uint32 { + range "1..max"; + } + units "seconds"; + default "600"; // 10 minutes + description + "The timeout interval for a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist { + if-feature confirmed-commit; + type string; + description + "This parameter is used to make a confirmed commit + persistent. A persistent confirmed commit is not aborted + if the NETCONF session terminates. The only way to abort + a persistent confirmed commit is to let the timer expire, + or to use the operation. + + The value of this parameter is a token that must be given + in the 'persist-id' parameter of or + operations in order to confirm or cancel + the persistent confirmed commit. + + The token should be a random string."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist-id { + if-feature confirmed-commit; + type string; + description + "This parameter is given in order to commit a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + reference "RFC 6241, Section 8.3.4.1"; + } + + } + } + + rpc discard-changes { + if-feature candidate; + + description + "Revert the candidate configuration to the current + running configuration."; + reference "RFC 6241, Section 8.3.4.2"; + } + + rpc cancel-commit { + if-feature confirmed-commit; + description + "This operation is used to cancel an ongoing confirmed commit. + If the confirmed commit is persistent, the parameter + 'persist-id' must be given, and it must match the value of the + 'persist' parameter."; + reference "RFC 6241, Section 8.4.4.1"; + + input { + leaf persist-id { + type string; + description + "This parameter is given in order to cancel a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + } + } + } + + rpc validate { + if-feature validate; + + description + "Validates the contents of the specified configuration."; + + reference "RFC 6241, Section 8.6.4.1"; + + input { + container source { + description + "Particular configuration to validate."; + + choice config-source { + mandatory true; + description + "The configuration source to validate."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } +}