summaryrefslogtreecommitdiff
path: root/src/commit/commit-algorithm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/commit/commit-algorithm.cpp')
-rw-r--r--src/commit/commit-algorithm.cpp1191
1 files changed, 1191 insertions, 0 deletions
diff --git a/src/commit/commit-algorithm.cpp b/src/commit/commit-algorithm.cpp
new file mode 100644
index 0000000..89a6dfe
--- /dev/null
+++ b/src/commit/commit-algorithm.cpp
@@ -0,0 +1,1191 @@
+/*
+ * Copyright (C) 2011 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <vector>
+#include <string>
+
+#include <cli_cstore.h>
+#include <commit/commit-algorithm.hpp>
+#include <cnode/cnode-algorithm.hpp>
+
+using namespace commit;
+using namespace std;
+
+
+////// static functions
+static void
+_set_node_commit_state(CfgNode& node, CommitState s, bool recursive)
+{
+ node.setCommitState(s);
+ if (recursive) {
+ for (size_t i = 0; i < node.numChildNodes(); i++) {
+ _set_node_commit_state(*(node.childAt(i)), s, recursive);
+ }
+ }
+}
+
+static void
+_set_node_commit_path(CfgNode& node, const Cpath &p, bool recursive)
+{
+ node.setCommitPath(p, node.isValue(), node.getValue(), node.getName());
+ if (recursive) {
+ for (size_t i = 0; i < node.numChildNodes(); i++) {
+ _set_node_commit_path(*(node.childAt(i)), node.getCommitPath(),
+ recursive);
+ }
+ }
+}
+
+static void
+_set_node_commit_child_delete_failed(CfgNode& node)
+{
+ // recursively bottom-up
+ node.setCommitChildDeleteFailed();
+ if (node.getParent()) {
+ _set_node_commit_child_delete_failed(*(node.getParent()));
+ }
+}
+
+static void
+_set_node_commit_create_failed(CfgNode& node)
+{
+ // recursively top-down
+ if (node.getCommitState() == COMMIT_STATE_ADDED) {
+ // only set failure if the node is being created
+ node.setCommitCreateFailed();
+ }
+ for (size_t i = 0; i < node.numChildNodes(); i++) {
+ _set_node_commit_create_failed(*(node.childAt(i)));
+ }
+}
+
+static size_t
+_get_num_commit_multi_values(const CfgNode& node)
+{
+ return (node.getCommitState() == COMMIT_STATE_CHANGED
+ ? node.numCommitMultiValues() : node.getValues().size());
+}
+
+static string
+_get_commit_multi_value_at(const CfgNode& node, size_t idx)
+{
+ return (node.getCommitState() == COMMIT_STATE_CHANGED
+ ? node.commitMultiValueAt(idx) : (node.getValues())[idx]);
+}
+
+static CommitState
+_get_commit_multi_state_at(const CfgNode& node, size_t idx)
+{
+ CommitState s = node.getCommitState();
+ return (s == COMMIT_STATE_CHANGED ? node.commitMultiStateAt(idx) : s);
+}
+
+// nodes other than "changed" leaf nodes
+static CfgNode *
+_create_commit_cfg_node(CfgNode& cn, const Cpath& p, CommitState s)
+{
+ CfgNode *node = new CfgNode(cn);
+ _set_node_commit_state(*node, s, (s != COMMIT_STATE_UNCHANGED));
+ _set_node_commit_path(*node, p, (s != COMMIT_STATE_UNCHANGED));
+ if (s == COMMIT_STATE_UNCHANGED) {
+ node->clearChildNodes();
+ } else {
+ for (size_t i = 0; i < node->numChildNodes(); i++) {
+ node->childAt(i)->setParent(node);
+ }
+ }
+ return node;
+}
+
+// "changed" multi-value leaf nodes
+static CfgNode *
+_create_commit_cfg_node(const CfgNode& cn, const Cpath& p,
+ const vector<string>& values,
+ const vector<CommitState>& states)
+{
+ CfgNode *node = new CfgNode(cn);
+ _set_node_commit_state(*node, COMMIT_STATE_CHANGED, false);
+ _set_node_commit_path(*node, p, false);
+ node->setCommitMultiValues(values, states);
+ return node;
+}
+
+// "changed" single-value leaf nodes
+static CfgNode *
+_create_commit_cfg_node(const CfgNode& cn, const Cpath& p, const string& val1,
+ const string& val2, bool def1, bool def2)
+{
+ CfgNode *node = new CfgNode(cn);
+ _set_node_commit_state(*node, COMMIT_STATE_CHANGED, false);
+ _set_node_commit_path(*node, p, false);
+ node->setCommitValue(val1, val2, def1, def2);
+ return node;
+}
+
+static void
+_get_commit_prio_subtrees(CfgNode *sroot, PrioNode& parent)
+{
+ if (!sroot) {
+ return;
+ }
+
+ PrioNode *pn = &parent;
+ // need a local copy since nodes can be detached
+ vector<CfgNode *> cnodes = sroot->getChildNodes();
+ if (sroot->getPriority() && (sroot->isValue() || !sroot->isTag())) {
+ // enforce hierarchical constraint
+ unsigned int prio = sroot->getPriority();
+ unsigned int pprio = parent.getPriority();
+ if (prio <= pprio) {
+ // can't have that => make it higher than parent priority
+ OUTPUT_USER("Warning: priority inversion [%s](%u) <= [%s](%u)\n"
+ " changing [%s] to (%u)\n",
+ sroot->getCommitPath().to_string().c_str(), prio,
+ parent.getCommitPath().to_string().c_str(), pprio,
+ sroot->getCommitPath().to_string().c_str(),
+ pprio + 1);
+ sroot->setPriority(pprio + 1);
+ }
+
+ // only non-"tag node" applies ("tag nodes" not used in prio tree)
+ pn = new PrioNode(sroot);
+ /* record the original parent in config tree. this will be used to
+ * enforce "hierarchical constraint" in the config. skip the tag node
+ * if this is a tag value since commit doesn't act on tag nodes.
+ */
+ CfgNode *pnode = sroot->getParent();
+ pn->setCfgParent(sroot->isTag() ? pnode->getParent() : pnode);
+ parent.addChildNode(pn);
+ sroot->detachFromParent();
+ }
+
+ for (size_t i = 0; i < cnodes.size(); i++) {
+ _get_commit_prio_subtrees(cnodes[i], *pn);
+ }
+}
+
+static void
+_get_commit_prio_queue(PrioNode *proot, PrioQueueT& pq, DelPrioQueueT& dpq)
+{
+ if (!proot) {
+ return;
+ }
+ if (proot->getCommitState() == COMMIT_STATE_DELETED) {
+ dpq.push(proot);
+ } else {
+ pq.push(proot);
+ }
+ for (size_t i = 0; i < proot->numChildNodes(); i++) {
+ _get_commit_prio_queue(proot->childAt(i), pq, dpq);
+ }
+}
+
+template<class N> static bool
+_trv_tag_node(N *node)
+{
+ return false;
+}
+template<> bool
+_trv_tag_node<CfgNode>(CfgNode *node)
+{
+ return node->isTagNode();
+}
+
+template<class N> static bool
+_trv_be_node(N *node)
+{
+ return false;
+}
+template<> bool
+_trv_be_node<CfgNode>(CfgNode *node)
+{
+ return node->isTagNode();
+}
+
+template<class N> static void
+_commit_tree_traversal(N *root, bool betree_only,
+ CommitTreeTraversalOrder order,
+ vector<N *>& nodelist, bool include_root = false,
+ bool init = true)
+{
+ /* note: commit traversal doesn't include "tag node", only "tag values".
+ * (this only applies when traversing CfgNode tree, not when
+ * traversing PrioNode tree, hence the specializations above.)
+ *
+ * also, "include_root" controls if the "original root" is included.
+ */
+ if (order == PRE_ORDER && !_trv_tag_node(root) && include_root) {
+ nodelist.push_back(root);
+ }
+ if (init || !betree_only || _trv_tag_node(root) || !_trv_be_node(root)) {
+ for (size_t i = 0; i < root->numChildNodes(); i++) {
+ _commit_tree_traversal(root->childAt(i), betree_only, order,
+ nodelist, true, false);
+ }
+ }
+ if (order == POST_ORDER && !_trv_tag_node(root) && include_root) {
+ nodelist.push_back(root);
+ }
+}
+
+static bool
+_exec_tmpl_actions(Cstore& cs, CommitState s, char *at_str,
+ const Cpath& path, const Cpath& disp_path,
+ const vtw_node *actions, const vtw_def *def)
+{
+ if (!actions) {
+ // no actions => success
+ return true;
+ }
+
+ /* XXX this follows the logic in original implementation since some
+ * features are using it.
+ */
+ const char *aenv = "ACTIVE";
+ bool in_del = false;
+ switch (s) {
+ case COMMIT_STATE_ADDED:
+ case COMMIT_STATE_CHANGED:
+ aenv = "SET";
+ break;
+ case COMMIT_STATE_DELETED:
+ aenv = "DELETE";
+ in_del = true;
+ break;
+ default:
+ break;
+ }
+ setenv("COMMIT_ACTION", aenv, 1);
+ set_in_delete_action(in_del);
+ bool ret = cs.executeTmplActions(at_str, path, disp_path, actions, def);
+ set_in_delete_action(false);
+ unsetenv("COMMIT_ACTION");
+ return ret;
+}
+
+/* execute the specified type of actions on the specified node.
+ *
+ * note that if the "committed list" clist is specified, no action will be
+ * performed except adding the node to the committed list (if needed).
+ */
+static bool
+_exec_node_actions(Cstore& cs, CfgNode& node, vtw_act_type act,
+ CommittedPathListT *clist = NULL)
+{
+ if (node.isMulti()) {
+ // fail if this is called with a multi node
+ OUTPUT_USER("_exec_node_actions() called with multi[%s]\n",
+ node.getCommitPath().to_string().c_str());
+ return false;
+ }
+
+ CommitState s = node.getCommitState();
+ if (s == COMMIT_STATE_DELETED && node.commitChildDeleteFailed()) {
+ return false;
+ }
+
+ bool nop = false;
+ if (!node.getActions(act)) {
+ // no actions => success
+ nop = true;
+ }
+
+ auto_ptr<char> at_str;
+ Cpath pcomps(node.getCommitPath());
+ tr1::shared_ptr<Cpath> pdisp(new Cpath(pcomps));
+ bool add_parent_to_committed = false;
+ if (node.isLeaf()) {
+ // single-value node
+ if (s == COMMIT_STATE_CHANGED) {
+ if (node.commitValueBefore() == node.commitValueAfter()) {
+ // value didn't change (only "default" status), so nop
+ nop = true;
+ }
+ at_str.reset(strdup(node.commitValueAfter().c_str()));
+ } else {
+ if (s == COMMIT_STATE_ADDED || s == COMMIT_STATE_DELETED) {
+ // add parent to "committed list" if it's added/deleted
+ add_parent_to_committed = true;
+ }
+ at_str.reset(strdup(node.getValue().c_str()));
+ }
+ pdisp->push(at_str.get());
+ } else if (node.isValue()) {
+ // tag value
+ at_str.reset(strdup(node.getValue().c_str()));
+ pcomps.pop(); // paths need to be at the "node" level
+ } else {
+ // typeless node
+ at_str.reset(strdup(node.getName().c_str()));
+ }
+
+ if (clist) {
+ /* note that even though every "tag value" will be added to the
+ * "committed list" here, simply adding the corresponding "tag node"
+ * here does not work.
+ *
+ * basically there are three scenarios for a tag node:
+ * (1) in both active and working
+ * i.e., tag node itself is unchanged => doesn't need to be added
+ * to committed list since "committed query" will check for this
+ * condition first.
+ * (2) only in working
+ * i.e., all tag values are being added so tag node itself is also
+ * being added. in this case, tag node must be considered
+ * "committed" after the first tag value has been processed
+ * successfully.
+ * (3) only in active
+ * i.e., all tag values are being deleted so is tag node itself.
+ * in this case, tag node must be considered "committed" only
+ * after all tag values have been processed successfully.
+ *
+ * cases (2) and (3) cannot be handled here since they depend on the
+ * processing order and outcome of siblings of tag values. therefore,
+ * tag node will never be added to the committed list, and the
+ * "committed query" function will need to handle tag nodes as special
+ * case.
+ */
+ if (add_parent_to_committed) {
+ tr1::shared_ptr<Cpath> ppdisp(new Cpath(pcomps));
+ clist->push_back(CommittedPathT(s, ppdisp));
+ }
+ clist->push_back(CommittedPathT(s, pdisp));
+ return true;
+ }
+
+ if (nop) {
+ // nothing to do
+ return true;
+ }
+
+ if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
+ node.getActions(act), node.getDef())) {
+ if (act == create_act) {
+ _set_node_commit_create_failed(node);
+ }
+ return false;
+ }
+ return true;
+}
+
+/* execute the specified type of actions on the specified "multi" node (i.e.,
+ * multi-value leaf node) during commit. act is one of "delete_act",
+ * "update_act", and "syntax_act", representing the different processing
+ * passes.
+ *
+ * see comment for _exec_node_actions() above about clist.
+ */
+static bool
+_exec_multi_node_actions(Cstore& cs, const CfgNode& node, vtw_act_type act,
+ CommittedPathListT *clist = NULL)
+{
+ if (!node.isMulti()) {
+ // fail if this is called with a non-multi node
+ OUTPUT_USER("_exec_multi_node_actions() called with non-multi[%s]\n",
+ node.getCommitPath().to_string().c_str());
+ return false;
+ }
+
+ const vtw_def *def = node.getDef();
+ Cpath pcomps(node.getCommitPath());
+ if (clist) {
+ CommitState s = node.getCommitState();
+ if (s == COMMIT_STATE_ADDED || s == COMMIT_STATE_DELETED) {
+ /* for multi-value leaf node, add the node itself to the
+ * "committed list" if it is added/deleted.
+ */
+ tr1::shared_ptr<Cpath> ppdisp(new Cpath(pcomps));
+ clist->push_back(CommittedPathT(s, ppdisp));
+ }
+ }
+ for (size_t i = 0; i < _get_num_commit_multi_values(node); i++) {
+ CommitState s = _get_commit_multi_state_at(node, i);
+ if (s == COMMIT_STATE_UNCHANGED) {
+ // nop for unchanged value
+ continue;
+ }
+ string v = _get_commit_multi_value_at(node, i);
+ auto_ptr<char> at_str(strdup(v.c_str()));
+ tr1::shared_ptr<Cpath> pdisp(new Cpath(pcomps));
+ pdisp->push(v);
+
+ if (clist) {
+ // add the value to the committed list
+ clist->push_back(CommittedPathT(s, pdisp));
+ continue;
+ }
+
+ if (act == syntax_act) {
+ // syntax pass
+ if (s != COMMIT_STATE_ADDED) {
+ continue;
+ }
+ if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
+ node.getActions(syntax_act), def)) {
+ return false;
+ }
+ } else {
+ //// delete or update pass
+ // begin
+ if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
+ node.getActions(begin_act), def)) {
+ return false;
+ }
+
+ if (act == delete_act) {
+ // delete pass
+ if (s == COMMIT_STATE_DELETED) {
+ if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
+ node.getActions(delete_act), def)) {
+ return false;
+ }
+ }
+ } else {
+ // update pass
+ if (s == COMMIT_STATE_ADDED) {
+ if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
+ node.getActions(create_act), def)) {
+ return false;
+ }
+ }
+ // no "CHANGED" for value of multi-value leaf node
+ }
+
+ // end
+ if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
+ node.getActions(end_act), def)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static void
+_set_commit_subtree_changed(CfgNode& node)
+{
+ // recursively bottom-up
+ if (node.commitSubtreeChanged()) {
+ // already set => terminate recursion
+ return;
+ }
+ node.setCommitSubtreeChanged();
+ if (node.getParent()) {
+ _set_commit_subtree_changed(*(node.getParent()));
+ }
+}
+
+static bool
+_commit_check_cfg_node(Cstore& cs, CfgNode *node, CommittedPathListT& clist)
+{
+ vector<CfgNode *> nodelist;
+ _commit_tree_traversal(node, false, PRE_ORDER, nodelist, true);
+ for (size_t i = 0; i < nodelist.size(); i++) {
+ CommitState s = nodelist[i]->getCommitState();
+ if (s == COMMIT_STATE_UNCHANGED) {
+ // nop
+ continue;
+ }
+ _set_commit_subtree_changed(*(nodelist[i]));
+
+ if (nodelist[i]->isMulti()) {
+ // for committed list processing, use top_act as dummy value
+ _exec_multi_node_actions(cs, *(nodelist[i]), top_act, &clist);
+ if (!_exec_multi_node_actions(cs, *(nodelist[i]), syntax_act)) {
+ return false;
+ }
+ continue;
+ }
+ if (s != COMMIT_STATE_UNCHANGED) {
+ // for committed list processing, use top_act as dummy value
+ _exec_node_actions(cs, *(nodelist[i]), top_act, &clist);
+ }
+ if (s == COMMIT_STATE_CHANGED || s == COMMIT_STATE_ADDED) {
+ if (!_exec_node_actions(cs, *(nodelist[i]), syntax_act)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static bool
+_commit_exec_cfg_node(Cstore& cs, CfgNode *node)
+{
+ if (!node->commitSubtreeChanged()) {
+ // nothing changed => nop
+ return true;
+ }
+
+ if (node->isMulti()) {
+ /* if reach here, this "multi" is being commited as a "top-level" node,
+ * so need to do both "delete pass" and "update pass".
+ */
+ return (_exec_multi_node_actions(cs, *node, delete_act)
+ ? (_exec_multi_node_actions(cs, *node, update_act)
+ ? true : false) : false);
+ }
+
+ // begin
+ if (!_exec_node_actions(cs, *node, begin_act)) {
+ return false;
+ }
+
+ // delete pass (bottom-up)
+ vector<CfgNode *> nodelist;
+ _commit_tree_traversal(node, true, POST_ORDER, nodelist);
+ for (size_t i = 0; i < nodelist.size(); i++) {
+ if (nodelist[i]->isMulti()) {
+ // do "delete pass" for "multi"
+ if (!_exec_multi_node_actions(cs, *(nodelist[i]), delete_act)) {
+ return false;
+ }
+ continue;
+ }
+
+ if (nodelist[i]->getCommitState() != COMMIT_STATE_DELETED) {
+ continue;
+ }
+ if (nodelist[i]->isBeginEndNode()) {
+ if (!_commit_exec_cfg_node(cs, nodelist[i])) {
+ return false;
+ }
+ } else {
+ if (!_exec_node_actions(cs, *(nodelist[i]), delete_act)) {
+ return false;
+ }
+ }
+ }
+
+ CommitState s = node->getCommitState();
+ if (s != COMMIT_STATE_UNCHANGED) {
+ if (s == COMMIT_STATE_DELETED) {
+ // delete self
+ if (!_exec_node_actions(cs, *node, delete_act)) {
+ return false;
+ }
+ } else {
+ // create/update self
+ vtw_act_type act = (s == COMMIT_STATE_ADDED ? create_act : update_act);
+ if (!_exec_node_actions(cs, *node, act)) {
+ return false;
+ }
+ }
+ }
+
+ // create/update pass (top-down)
+ nodelist.clear();
+ _commit_tree_traversal(node, true, PRE_ORDER, nodelist);
+ for (size_t i = 0; i < nodelist.size(); i++) {
+ if (nodelist[i]->isMulti()) {
+ // do "update pass" for "multi"
+ if (!_exec_multi_node_actions(cs, *(nodelist[i]), update_act)) {
+ return false;
+ }
+ continue;
+ }
+
+ CommitState sc = nodelist[i]->getCommitState();
+ if (sc == COMMIT_STATE_DELETED) {
+ // deleted nodes already handled in previous loop
+ continue;
+ }
+ if (nodelist[i]->isBeginEndNode()) {
+ if (!_commit_exec_cfg_node(cs, nodelist[i])) {
+ return false;
+ }
+ } else if (sc == COMMIT_STATE_UNCHANGED) {
+ continue;
+ } else {
+ // added or changed
+ vtw_act_type act = (sc == COMMIT_STATE_ADDED ? create_act : update_act);
+ if (!_exec_node_actions(cs, *(nodelist[i]), act)) {
+ return false;
+ }
+ }
+ }
+
+ // end
+ if (!_exec_node_actions(cs, *node, end_act)) {
+ return false;
+ }
+ return true;
+}
+
+static bool
+_commit_exec_prio_subtree(Cstore& cs, PrioNode *proot)
+{
+ CfgNode *cfg = proot->getCfgNode();
+ if (cfg) {
+ if (proot->getCommitState() == COMMIT_STATE_ADDED
+ && proot->parentCreateFailed()) {
+ // can't create if parent create failed
+ proot->setSucceeded(false);
+ return false;
+ }
+ CommittedPathListT clist;
+ if (!_commit_check_cfg_node(cs, cfg, clist)
+ || !_commit_exec_cfg_node(cs, cfg)) {
+ // subtree commit failed
+ proot->setSucceeded(false);
+ return false;
+ }
+ // subtree succeeded, mark nodes committed
+ for (size_t i = 0; i < clist.size(); i++) {
+ if (!cs.markCfgPathCommitted(*(clist[i].second.get()),
+ (clist[i].first
+ == COMMIT_STATE_DELETED))) {
+ fprintf(stderr, "Failed to mark path committed\n");
+ proot->setSucceeded(false);
+ return false;
+ }
+ }
+ }
+ proot->setSucceeded(true);
+ return true;
+}
+
+static CfgNode *
+_get_commit_leaf_node(CfgNode *cfg1, CfgNode *cfg2, const Cpath& cur_path,
+ bool& is_leaf)
+{
+ if ((cfg1 && !cfg1->isLeaf()) || (cfg2 && !cfg2->isLeaf())) {
+ // not a leaf node
+ is_leaf = false;
+ return NULL;
+ }
+
+ is_leaf = true;
+
+ if (!cfg1) {
+ return _create_commit_cfg_node(*cfg2, cur_path, COMMIT_STATE_ADDED);
+ } else if (!cfg2) {
+ return _create_commit_cfg_node(*cfg1, cur_path, COMMIT_STATE_DELETED);
+ }
+
+ if (cfg1->isMulti()) {
+ // multi-value node
+ vector<string> values;
+ vector<DiffState> pfxs;
+ if (!cnode::cmp_multi_values(cfg1, cfg2, values, pfxs)) {
+ // no change
+ return NULL;
+ }
+ vector<CommitState> states;
+ for (size_t i = 0; i < pfxs.size(); i++) {
+ if (pfxs[i] == DIFF_ADD) {
+ states.push_back(COMMIT_STATE_ADDED);
+ } else if (pfxs[i] == DIFF_DEL) {
+ states.push_back(COMMIT_STATE_DELETED);
+ } else {
+ // "changed" for "show" is really "unchanged" for "commit"
+ states.push_back(COMMIT_STATE_UNCHANGED);
+ }
+ }
+ return _create_commit_cfg_node(*cfg1, cur_path, values, states);
+ } else {
+ // single-value node
+ string val1 = cfg1->getValue();
+ string val2 = cfg2->getValue();
+ bool def1 = cfg1->isDefault();
+ bool def2 = cfg2->isDefault();
+ if (val1 == val2 && def1 == def2) {
+ // no change
+ return NULL;
+ }
+ return _create_commit_cfg_node(*cfg1, cur_path, val1, val2, def1, def2);
+ }
+}
+
+static CfgNode *
+_get_commit_other_node(CfgNode *cfg1, CfgNode *cfg2, const Cpath& cur_path)
+{
+ string name, value;
+ bool not_tag_node, is_value, is_leaf_typeless;
+ vector<CfgNode *> rcnodes1, rcnodes2;
+ cnode::cmp_non_leaf_nodes(cfg1, cfg2, rcnodes1, rcnodes2, not_tag_node,
+ is_value, is_leaf_typeless, name, value);
+
+ if (!cfg1) {
+ return _create_commit_cfg_node(*cfg2, cur_path, COMMIT_STATE_ADDED);
+ } else if (!cfg2) {
+ return _create_commit_cfg_node(*cfg1, cur_path, COMMIT_STATE_DELETED);
+ }
+
+ CfgNode *cn = _create_commit_cfg_node(*cfg1, cur_path,
+ COMMIT_STATE_UNCHANGED);
+ for (size_t i = 0; i < rcnodes1.size(); i++) {
+ CfgNode *cnode = getCommitTree(rcnodes1[i], rcnodes2[i],
+ cn->getCommitPath());
+ if (cnode) {
+ cn->addChildNode(cnode);
+ }
+ }
+ if (cn->numChildNodes() < 1) {
+ delete cn;
+ return NULL;
+ }
+ return cn;
+}
+
+
+////// class CommitData
+CommitData::CommitData()
+ : _commit_state(COMMIT_STATE_UNCHANGED), _commit_create_failed(false),
+ _commit_child_delete_failed(false), _commit_subtree_changed(false)
+{
+}
+
+// member setters
+void
+CommitData::setCommitState(CommitState s)
+{
+ _commit_state = s;
+}
+
+void
+CommitData::setCommitPath(const Cpath& p, bool is_val, const string& val,
+ const string& name)
+{
+ Cpath cp(p);
+ if (is_val) {
+ cp.push(val);
+ } else if (name.size() > 0) {
+ cp.push(name);
+ }
+ _commit_path = cp;
+}
+
+void
+CommitData::setCommitMultiValues(const vector<string>& values, const vector<CommitState>& states)
+{
+ _commit_values = values;
+ _commit_values_states = states;
+}
+
+void
+CommitData::setCommitValue(const string& val1, const string& val2, bool def1, bool def2)
+{
+ _commit_value.first = val1;
+ _commit_value.second = val2;
+ _commit_default.first = def1;
+ _commit_default.second = def2;
+}
+
+void
+CommitData::setCommitChildDeleteFailed()
+{
+ _commit_child_delete_failed = true;
+}
+
+void
+CommitData::setCommitCreateFailed()
+{
+ _commit_create_failed = true;
+}
+
+void
+CommitData::setCommitSubtreeChanged()
+{
+ _commit_subtree_changed = true;
+}
+
+// member getters
+CommitState
+CommitData::getCommitState() const
+{
+ return _commit_state;
+}
+
+Cpath
+CommitData::getCommitPath() const
+{
+ return _commit_path;
+}
+
+size_t
+CommitData::numCommitMultiValues() const
+{
+ return _commit_values.size();
+}
+
+string
+CommitData::commitMultiValueAt(size_t idx) const
+{
+ return _commit_values[idx];
+}
+
+CommitState
+CommitData::commitMultiStateAt(size_t idx) const
+{
+ return _commit_values_states[idx];
+}
+
+string
+CommitData::commitValueBefore() const
+{
+ return _commit_value.first;
+}
+
+string
+CommitData::commitValueAfter() const
+{
+ return _commit_value.second;
+}
+
+bool
+CommitData::commitChildDeleteFailed() const
+{
+ return _commit_child_delete_failed;
+}
+
+bool
+CommitData::commitCreateFailed() const
+{
+ return _commit_create_failed;
+}
+
+bool
+CommitData::commitSubtreeChanged() const
+{
+ return _commit_subtree_changed;
+}
+
+// member functions for tmpl stuff
+void
+CommitData::setTmpl(tr1::shared_ptr<Ctemplate> def)
+{
+ _def = def;
+}
+
+tr1::shared_ptr<Ctemplate>
+CommitData::getTmpl() const
+{
+ return _def;
+}
+
+const vtw_def *
+CommitData::getDef() const
+{
+ return (_def.get() ? _def.get()->getDef() : NULL);
+}
+
+unsigned int
+CommitData::getPriority() const
+{
+ return (_def.get() ? _def->getPriority() : 0);
+}
+
+void
+CommitData::setPriority(unsigned int p)
+{
+ if (!_def.get()) {
+ return;
+ }
+ _def->setPriority(p);
+}
+
+const vtw_node *
+CommitData::getActions(vtw_act_type act, bool raw) const
+{
+ if (!_def.get()) {
+ return NULL;
+ }
+ if (!raw && act == create_act && !_def->getActions(act)) {
+ act = update_act;
+ }
+ return _def->getActions(act);
+}
+
+bool
+CommitData::isBeginEndNode() const
+{
+ return (getActions(begin_act) || getActions(end_act));
+}
+
+
+////// class PrioNode
+PrioNode::PrioNode(CfgNode *n)
+ : TreeNode<PrioNode>(), _node(n), _cfg_parent(0),
+ _succeeded(true), _subtree_failure(false), _subtree_success(false)
+{
+}
+
+CfgNode *
+PrioNode::getCfgNode()
+{
+ return _node;
+}
+
+unsigned int
+PrioNode::getPriority() const
+{
+ return (_node ? _node->getPriority() : 0);
+}
+
+CommitState
+PrioNode::getCommitState() const
+{
+ return (_node ? _node->getCommitState() : COMMIT_STATE_UNCHANGED);
+}
+
+Cpath
+PrioNode::getCommitPath() const
+{
+ return (_node ? _node->getCommitPath() : Cpath());
+}
+
+bool
+PrioNode::parentCreateFailed() const
+{
+ return (_cfg_parent ? _cfg_parent->commitCreateFailed() : false);
+}
+
+bool
+PrioNode::succeeded() const
+{
+ return _succeeded;
+}
+
+bool
+PrioNode::hasSubtreeFailure() const
+{
+ return _subtree_failure;
+}
+
+bool
+PrioNode::hasSubtreeSuccess() const
+{
+ return _subtree_success;
+}
+
+void
+PrioNode::setCfgParent(CfgNode *p)
+{
+ _cfg_parent = p;
+}
+
+void
+PrioNode::setSucceeded(bool succeeded)
+{
+ if (succeeded) {
+ if (getParent()) {
+ getParent()->setSubtreeSuccess();
+ }
+ return;
+ }
+
+ // failed
+ _succeeded = false;
+ if (getParent()) {
+ getParent()->setSubtreeFailure();
+ }
+ if (getCommitState() == COMMIT_STATE_DELETED && _cfg_parent) {
+ /* this will recursively set child_delete_failed on the "config parent"
+ * (which should be in the parent prio subtree) and all its ancestors.
+ * it will be used to prevent anything above this from being deleted
+ * so that the hierarchical structure can be preserved.
+ */
+ _set_node_commit_child_delete_failed(*_cfg_parent);
+ }
+ if (_node) {
+ /* this will recursively set create_failed for all nodes that are "being
+ * created", i.e., in COMMIT_STATE_ADDED state.
+ * in other words, if a prio subtree fails, any "create" in the subtree
+ * is considered failed and therefore any prio subtree under those
+ * cannot be created.
+ */
+ _set_node_commit_create_failed(*_node);
+ }
+}
+
+void
+PrioNode::setSubtreeFailure()
+{
+ if (_subtree_failure) {
+ // already set => terminate recursion
+ return;
+ }
+
+ _subtree_failure = true;
+ if (getParent()) {
+ getParent()->setSubtreeFailure();
+ }
+}
+
+void
+PrioNode::setSubtreeSuccess()
+{
+ if (_subtree_success) {
+ // already set => terminate recursion
+ return;
+ }
+
+ _subtree_success = true;
+ if (getParent()) {
+ getParent()->setSubtreeSuccess();
+ }
+}
+
+
+////// exported functions
+CfgNode *
+commit::getCommitTree(CfgNode *cfg1, CfgNode *cfg2, const Cpath& cur_path)
+{
+ // if doesn't exist or is deactivated, treat as NULL
+ if (cfg1 && (!cfg1->exists() || cfg1->isDeactivated()) ) {
+ cfg1 = NULL;
+ }
+ if (cfg2 && (!cfg2->exists() || cfg2->isDeactivated())) {
+ cfg2 = NULL;
+ }
+
+ if (!cfg1 && !cfg2) {
+ fprintf(stderr, "getCommitTree error (both config NULL)\n");
+ exit(1);
+ }
+
+ bool is_leaf = false;
+ CfgNode *cn = _get_commit_leaf_node(cfg1, cfg2, cur_path, is_leaf);
+ if (!is_leaf) {
+ // intermediate node, tag node, or tag value
+ cn = _get_commit_other_node(cfg1, cfg2, cur_path);
+ }
+ return cn;
+}
+
+bool
+commit::isCommitPathEffective(Cstore& cs, const Cpath& pcomps,
+ tr1::shared_ptr<Ctemplate> def,
+ bool in_active, bool in_working)
+{
+ if (in_active && in_working) {
+ // remain the same
+ return true;
+ }
+ if (!in_active && !in_working) {
+ // doesn't exist
+ return false;
+ }
+ // at this point, in_active corresponds to "being deleted"
+
+ if (def->isTagNode()) {
+ // special handling for tag nodes, which are never marked
+ vector<string> tvals;
+ // get tag values from active or working config
+ cs.cfgPathGetChildNodes(pcomps, tvals, in_active);
+ Cpath vpath(pcomps);
+ /* note that there should be at least 1 tag value since tag node
+ * cannot exist without tag value.
+ */
+ for (size_t i = 0; i < tvals.size(); i++) {
+ vpath.push(tvals[i]);
+ if (in_active) {
+ // being deleted => all tag values are being deleted
+ if (!cs.cfgPathMarkedCommitted(vpath, true)) {
+ /* a tag value is not marked committed
+ * => a tag value has not been deleted
+ * => tag node has not been deleted
+ * => still effective
+ */
+ return true;
+ }
+ } else {
+ // being added => all tag values are being added
+ if (cs.cfgPathMarkedCommitted(vpath, false)) {
+ /* a tag value is marked committed
+ * => a tag value has been added
+ * => tag node has been added
+ * => already effective
+ */
+ return true;
+ }
+ }
+ vpath.pop();
+ }
+ // not effective
+ return false;
+ }
+
+ /* if not tag node, effectiveness corresponds to committed marking:
+ * if deleted (i.e., in_active), then !marked is effective
+ * otherwise (i.e., added), marked is effective
+ */
+ bool marked = cs.cfgPathMarkedCommitted(pcomps, in_active);
+ return (in_active ? !marked : marked);
+}
+
+bool
+commit::doCommit(Cstore& cs, CfgNode& cfg1, CfgNode& cfg2)
+{
+ Cpath p;
+ CfgNode *root = getCommitTree(&cfg1, &cfg2, p);
+ if (!root) {
+ OUTPUT_USER("No configuration changes to commit\n");
+ return true;
+ }
+
+ set_in_commit(true);
+
+ PrioNode proot(root); // proot corresponds to root
+ _get_commit_prio_subtrees(root, proot);
+ // at this point all prio nodes have been detached from root
+ PrioQueueT pq;
+ DelPrioQueueT dpq;
+ _get_commit_prio_queue(&proot, pq, dpq);
+ size_t s = 0, f = 0;
+ while (!dpq.empty()) {
+ PrioNode *p = dpq.top();
+ if (!_commit_exec_prio_subtree(cs, p)) {
+ // prio subtree failed
+ ++f;
+ } else {
+ // succeeded
+ ++s;
+ }
+ dpq.pop();
+ }
+ while (!pq.empty()) {
+ PrioNode *p = pq.top();
+ if (!_commit_exec_prio_subtree(cs, p)) {
+ // prio subtree failed
+ ++f;
+ } else {
+ // succeeded
+ ++s;
+ }
+ pq.pop();
+ }
+ bool ret = true;
+ if (f > 0) {
+ OUTPUT_USER("Commit failed\n");
+ ret = false;
+ }
+
+ if (!cs.commitConfig(proot)) {
+ OUTPUT_USER("Failed to generate committed config\n");
+ ret = false;
+ }
+
+ set_in_commit(false);
+ if (!cs.clearCommittedMarkers()) {
+ OUTPUT_USER("Failed to clear committed markers\n");
+ ret = false;
+ }
+ return ret;
+}
+