diff --git a/apps/backend/backend_commit.c b/apps/backend/backend_commit.c index 7d5b60c5..12a3b51d 100644 --- a/apps/backend/backend_commit.c +++ b/apps/backend/backend_commit.c @@ -248,7 +248,10 @@ startup_common(clixon_handle h, else if (xml_sort_recurse(xt) < 0) { clixon_err(OE_XML, EFAULT, "Yang sort error"); } - if (xmldb_dump(h, stdout, xt) < 0) + /* clear XML tree of defaults */ + if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0) + goto done; + if (xmldb_dump(h, stdout, xt, WITHDEFAULTS_REPORT_ALL) < 0) goto done; exit(0); /* This is fairly abrupt , but need to avoid side-effects of rewinding stack. Alternative is to make a separate function stack for this. */ @@ -488,6 +491,12 @@ validate_common(clixon_handle h, clixon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; } + if (xmldb_cache_get(h, db) != NULL){ + if (xmldb_populate(h, db) < 0) + goto done; + if (xmldb_write_cache2file(h, db) < 0) + goto done; + } /* This is the state we are going to */ if ((ret = xmldb_get0(h, db, YB_MODULE, NULL, "/", 0, 0, &td->td_target, NULL, xret)) < 0) goto done; diff --git a/apps/backend/backend_get.c b/apps/backend/backend_get.c index 947acc2b..462721ed 100644 --- a/apps/backend/backend_get.c +++ b/apps/backend/backend_get.c @@ -390,6 +390,7 @@ filter_xpath_again(clixon_handle h, * @param[in] nsc Namespace context of xpath * @param[in] username User name for NACM access * @param[in] depth Nr of levels to print, -1 is all, 0 is none + * @param[in] wdef With-defaults parameter * @param[out] cbret Return xml tree, eg ..., #include #include -#include #include #include #include diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index 0d7c8cd4..6efb182e 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -46,7 +46,6 @@ #include #include #include -#include #include #include #include @@ -454,6 +453,7 @@ choice_delete_other(cxobj *x0, * In an "ordered-by user" list, the attributes "insert" and "key" in * the YANG XML namespace can be used to control where * in the list the entry is inserted. + * Marks added objects with XML_FLAG_ADD */ static int text_modify(clixon_handle h, @@ -617,7 +617,6 @@ text_modify(clixon_handle h, if ((x0 = xml_new(x1name, NULL, CX_ELMNT)) == NULL) goto done; xml_spec_set(x0, y0); - /* Get namespace from x1 * Check if namespace exists in x0 parent * if not add new binding and replace in x0. @@ -698,6 +697,7 @@ text_modify(clixon_handle h, if (changed){ if (xml_insert(x0p, x0, insert, valstr, NULL) < 0) goto done; + xml_flag_set(x0, XML_FLAG_ADD); } break; case OP_DELETE: @@ -718,8 +718,10 @@ text_modify(clixon_handle h, /* Purge if x1 value is NULL(match-all) or both values are equal */ if ((x1bstr == NULL) || ((x0bstr=xml_body(x0)) != NULL && strcmp(x0bstr, x1bstr)==0)){ + if (xml_purge(x0) < 0) goto done; + xml_flag_set(x0p, XML_FLAG_DEL); } else { if (op == OP_DELETE){ @@ -947,7 +949,7 @@ text_modify(clixon_handle h, #endif if (xml_insert(x0p, x0, insert, keystr, nscx1) < 0) goto done; - + xml_flag_set(x0, XML_FLAG_ADD); } break; case OP_DELETE: @@ -966,6 +968,7 @@ text_modify(clixon_handle h, } if (xml_purge(x0) < 0) goto done; + xml_flag_set(x0p, XML_FLAG_DEL); } break; default: @@ -1163,6 +1166,30 @@ text_modify_top(clixon_handle h, goto done; } /* text_modify_top */ +/*! Callback function type for xml_apply + * + * @param[in] x XML node + * @param[in] arg General-purpose argument + * @retval 2 Locally abort this subtree, continue with others + * @retval 1 Abort, dont continue with others, return 1 to end user + * @retval 0 OK, continue + * @retval -1 Error, aborted at first error encounter, return -1 to end user + * @note WHEN node are not checked, but should be updated when doing validate. The reason is + * that clixon needs a global traversal to re-evaluate WHEN nodes depending on changed targets + */ +static int +xml_mark_added_ancestors(cxobj *x, + void *arg) +{ + int flags = (intptr_t)arg; + + if (xml_flag(x, flags)){ + xml_apply_ancestor(x, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE); + return 2; + } + return 0; +} + /*! Modify database given an xml tree and an operation * * @param[in] h CLICON handle @@ -1197,20 +1224,15 @@ xmldb_put(clixon_handle h, cbuf *cbret) { int retval = -1; - char *dbfile = NULL; - FILE *f = NULL; yang_stmt *yspec; cxobj *x0 = NULL; db_elmnt *de = NULL; + db_elmnt de0 = {0,}; int ret; cxobj *xnacm = NULL; - cxobj *xmodst = NULL; - cxobj *x; int permit = 0; /* nacm permit all */ - char *format; cvec *nsc = NULL; /* nacm namespace context */ int firsttime = 0; - int pretty; cxobj *xerr = NULL; clixon_debug(CLIXON_DBG_DATASTORE|CLIXON_DBG_DETAIL, "db %s", db); @@ -1245,7 +1267,6 @@ xmldb_put(clixon_handle h, xml_name(x0), DATASTORE_TOP_SYMBOL); goto done; } - /* XXX Ad-hoc: * In yang mounts yang specs may not be available * in initial parsing, but may be at a later stage. @@ -1256,13 +1277,8 @@ xmldb_put(clixon_handle h, goto done; } /* Here x0 looks like: ... */ -#if 0 /* debug */ - if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0) - clixon_log(h, LOG_NOTICE, "%s: verify failed #1", __FUNCTION__); -#endif xnacm = clicon_nacm_cache(h); permit = (xnacm==NULL); - /* Here assume if xnacm is set and !permit do NACM */ clicon_data_del(h, "objectexisted"); /* @@ -1283,11 +1299,10 @@ xmldb_put(clixon_handle h, /* Remove NONE nodes if all subs recursively are also NONE */ 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|XML_FLAG_MARK)) < 0) + if (xml_apply(x0, CX_ELMNT, xml_mark_added_ancestors, (void*)(XML_FLAG_ADD|XML_FLAG_DEL)) < 0) goto done; /* Remove empty non-presence containers recursively. - * XXX should really be done for only new data in text_modify + * XXX should use xml_Mark_added_ancestors algorithm that xml_default_recurse uses for only */ if (xml_defaults_nopresence(x0, 3) < 0) goto done; @@ -1296,105 +1311,31 @@ xmldb_put(clixon_handle h, if (xml_global_defaults(h, x0, nsc, "/", yspec, 0) < 0) goto done; /* Add default recursive values */ - if (xml_default_recurse(x0, 0) < 0) + if (xml_default_recurse_flag(x0, 0, XML_FLAG_ADD|XML_FLAG_DEL) < 0) + goto done; + /* Clear flags from previous steps */ + if (xml_apply(x0, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, + (void*)(XML_FLAG_NONE|XML_FLAG_ADD|XML_FLAG_DEL|XML_FLAG_CHANGE)) < 0) goto done; /* Write back to datastore cache if first time */ - { - db_elmnt de0 = {0,}; - if (de != NULL) - de0 = *de; - if (de0.de_xml == NULL) - de0.de_xml = x0; - de0.de_empty = (xml_child_nr(de0.de_xml) == 0); - clicon_db_elmnt_set(h, db, &de0); - } - if (xmldb_db2file(h, db, &dbfile) < 0) - goto done; - if (dbfile==NULL){ - clixon_err(OE_XML, 0, "dbfile NULL"); - goto done; - } - /* Add module revision info before writing to file) - * Only if CLICON_XMLDB_MODSTATE is set - */ - if ((x = clicon_modst_cache_get(h, 1)) != NULL){ - if ((xmodst = xml_dup(x)) == NULL) - goto done; - if (xml_addsub(x0, xmodst) < 0) - goto done; - } - if ((format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) == NULL){ - clixon_err(OE_CFG, ENOENT, "No CLICON_XMLDB_FORMAT"); - goto done; - } - if ((f = fopen(dbfile, "w")) == NULL){ - clixon_err(OE_CFG, errno, "Creating file %s", dbfile); - goto done; - } - pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY"); - if (strcmp(format, "json")==0){ - if (clixon_json2file(f, x0, pretty, fprintf, 0, 0) < 0) - goto done; - } - else if (clixon_xml2file1(f, x0, 0, pretty, NULL, fprintf, 0, 0, WITHDEFAULTS_EXPLICIT) < 0) - goto done; - /* Remove modules state after writing to file - */ - if (xmodst && xml_purge(xmodst) < 0) + if (de != NULL) + de0 = *de; + if (de0.de_xml == NULL) + de0.de_xml = x0; + de0.de_empty = (xml_child_nr(de0.de_xml) == 0); + clicon_db_elmnt_set(h, db, &de0); + /* Write cache to file */ + if (xmldb_write_cache2file(h, db) < 0) goto done; retval = 1; done: clixon_debug(CLIXON_DBG_DATASTORE | CLIXON_DBG_DETAIL, "retval:%d", retval); - if (f != NULL) - fclose(f); if (xerr) xml_free(xerr); if (nsc) xml_nsctx_free(nsc); - if (dbfile) - free(dbfile); return retval; fail: retval = 0; goto done; } - -/* Dump a datastore to file including modstate - */ -int -xmldb_dump(clixon_handle h, - FILE *f, - cxobj *xt) -{ - int retval = -1; - cxobj *x; - cxobj *xmodst = NULL; - char *format; - int pretty; - - /* clear XML tree of defaults */ - if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0) - goto done; - /* Add modstate first */ - if ((x = clicon_modst_cache_get(h, 1)) != NULL){ - if ((xmodst = xml_dup(x)) == NULL) - goto done; - if (xml_child_insert_pos(xt, xmodst, 0) < 0) - goto done; - } - if ((format = clicon_option_str(h, "CLICON_XMLDB_FORMAT")) == NULL){ - clixon_err(OE_CFG, ENOENT, "No CLICON_XMLDB_FORMAT"); - goto done; - } - pretty = clicon_option_bool(h, "CLICON_XMLDB_PRETTY"); - if (strcmp(format,"json")==0){ - if (clixon_json2file(f, xt, pretty, fprintf, 0, 0) < 0) - goto done; - } - else if (clixon_xml2file(f, xt, 0, pretty, NULL, fprintf, 0, 0) < 0) - goto done; - retval = 0; - done: - return retval; -} - diff --git a/lib/src/clixon_err.c b/lib/src/clixon_err.c index 87a599ce..15ecb25e 100644 --- a/lib/src/clixon_err.c +++ b/lib/src/clixon_err.c @@ -439,6 +439,7 @@ clixon_err_fn(clixon_handle h, } } va_start(ap, format); + /* This checks for customizable errors */ if (clixon_plugin_errmsg_all(h, fn, line, LOG_TYPE_ERR, &category, &suberr, xerr, format, ap, &cb) < 0) goto done; diff --git a/lib/src/clixon_xml.c b/lib/src/clixon_xml.c index 01d8c09c..a60475df 100644 --- a/lib/src/clixon_xml.c +++ b/lib/src/clixon_xml.c @@ -1044,15 +1044,21 @@ xml_child_append(cxobj *xp, return 0; } -/*! Insert child xc at position i under parent xp +/*! Insert child XML at specific position under XML parent * + * @param[in] xp Parent XML node + * @param[in] xc Child XML node + * @param[in] pos Position + * @retval 0 OK + * @retval -1 Error + * @see xml_child_append * @note does not do anything with child, you may need to set its parent, etc */ int xml_child_insert_pos(cxobj *xp, cxobj *xc, - int i) + int pos) { size_t size; @@ -1070,9 +1076,9 @@ xml_child_insert_pos(cxobj *xp, return -1; } } - size = (xml_child_nr(xp) - i - 1)*sizeof(cxobj *); - memmove(&xp->x_childvec[i+1], &xp->x_childvec[i], size); - xp->x_childvec[i] = xc; + size = (xml_child_nr(xp) - pos - 1)*sizeof(cxobj *); + memmove(&xp->x_childvec[pos+1], &xp->x_childvec[pos], size); + xp->x_childvec[pos] = xc; return 0; } diff --git a/lib/src/clixon_xml_default.c b/lib/src/clixon_xml_default.c index 84ada2c1..4828f1a2 100644 --- a/lib/src/clixon_xml_default.c +++ b/lib/src/clixon_xml_default.c @@ -312,6 +312,7 @@ xml_default(yang_stmt *yt, case Y_CASE: yc = NULL; while ((yc = yn_each(yt, yc)) != NULL) { + // XXX consider only data nodes for optimization? /* If config parameter and local is config false */ if (!state && !yang_config(yc)) continue; @@ -349,6 +350,8 @@ xml_default(yang_stmt *yt, /* If this is non-presence, (and it does not exist in xt) call * recursively and create nodes if any default value exist first. * Then continue and populate? + * Also this code expands some "when" statements that have nothing to do with + * defaults. */ if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){ /* No such container exist, recursively try if needed */ @@ -396,12 +399,41 @@ xml_default(yang_stmt *yt, int xml_default_recurse(cxobj *xn, int state) +{ + return xml_default_recurse_flag(xn, state, 0x0); +} + +/*! Selectively recursively fill in default values in an XML tree using flags + * + * Skip nodes that are not either CHANGE or "flag" (typically ADD|DEL) + * When ADD is encountered process all children. + * This will process all nodes that lead to ADD nodes and skip others. + * @param[in] xt XML tree + * @param[in] state If set expand defaults also for state data, otherwise only config + * @param[in] flag If set only traverse nodes marked with flag (or CHANGE) + * @retval 0 OK + * @retval -1 Error + * @see xml_default_recurse + */ +int +xml_default_recurse_flag(cxobj *xn, + int state, + int flag) { int retval = -1; yang_stmt *yn; cxobj *x; yang_stmt *y; + if (flag){ + if (xml_flag(xn, XML_FLAG_CHANGE) != 0) + ; /* continue */ + else if (xml_flag(xn, flag) != 0){ + flag = 0x0; /* Pass all */ + } + else + goto skip; + } if ((yn = (yang_stmt*)xml_spec(xn)) != NULL){ if (xml_default(yn, xn, state) < 0) goto done; @@ -412,9 +444,10 @@ xml_default_recurse(cxobj *xn, if (!state && !yang_config(y)) continue; } - if (xml_default_recurse(x, state) < 0) + if (xml_default_recurse_flag(x, state, flag) < 0) goto done; } + skip: retval = 0; done: return retval; @@ -457,6 +490,7 @@ xml_global_defaults_create(cxobj *xt, * @param[in] xpath Filter global defaults with this and merge with xt * @param[in] yspec Top-level YANG specification tree, all modules * @param[in] state Set if global state, otherwise config + * @param[in] flags Only traverse nodes where flag is set * @retval 0 OK * @retval -1 Error * Uses cache? diff --git a/test/test_leaf_default.sh b/test/test_leaf_default.sh index 8060ef17..8aca7bdf 100755 --- a/test/test_leaf_default.sh +++ b/test/test_leaf_default.sh @@ -172,6 +172,9 @@ expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" " new "Set s3 to 99" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "99" "" "" +new "commit" +expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "" + new "get config np3 with npleaf and npext" expecteof_netconf "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO" "" "" "99" diff --git a/test/test_yang_with_defaults.sh b/test/test_yang_with_defaults.sh index d7e0a5f8..0de6f2f4 100755 --- a/test/test_yang_with_defaults.sh +++ b/test/test_yang_with_defaults.sh @@ -50,11 +50,8 @@ EOF # data model. cat < $fyang module example { - namespace "http://example.com/ns/interfaces"; - prefix exam; - typedef status-type { description "Interface status"; type enumeration { @@ -66,7 +63,6 @@ module example { } default ok; } - container interfaces { description "Example interfaces group";