/*
* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
using namespace commit;
using namespace std;
////// static
static const char *commit_hook_dirs[3] = {
"/etc/commit/pre-hooks.d",
"/etc/commit/post-hooks.d",
NULL
};
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. note that "changed" state applies to
* the "node" itself. the actual states of the node values can be added,
* deleted, changed, or unchanged.
*/
static CfgNode *
_create_commit_cfg_node(const CfgNode& cn, const Cpath& p,
const vector& values,
const vector& 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 (this does apply to the value)
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 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 static bool
_trv_tag_node(N *node)
{
return false;
}
template<> bool
_trv_tag_node(CfgNode *node)
{
return node->isTagNode();
}
template static bool
_trv_be_node(N *node)
{
return false;
}
template<> bool
_trv_be_node(CfgNode *node)
{
return node->isBeginEndNode();
}
template static void
_commit_tree_traversal(N *root, bool betree_only,
CommitTreeTraversalOrder order,
vector& 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 CfgNode& node, vtw_act_type act,
const vtw_def *def)
{
const vtw_node *actions = node.getActions(act);
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";
switch (s) {
case COMMIT_STATE_ADDED:
case COMMIT_STATE_CHANGED:
aenv = "SET";
break;
case COMMIT_STATE_DELETED:
aenv = "DELETE";
break;
default:
break;
}
setenv("COMMIT_ACTION", aenv, 1);
set_in_delete_action((act == delete_act));
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;
}
#if __GNUC__ < 6
auto_ptr at_str;
#else
unique_ptr at_str;
#endif
Cpath pcomps(node.getCommitPath());
tr1::shared_ptr 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 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, 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 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);
#if __GNUC__ < 6
auto_ptr at_str(strdup(v.c_str()));
#else
unique_ptr at_str(strdup(v.c_str()));
#endif
tr1::shared_ptr 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, syntax_act, def)) {
return false;
}
} else {
//// delete or update pass
// begin
if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
node, begin_act, def)) {
return false;
}
/* note that for CHANGED value we need to do BOTH a delete AND a
* create. this is the fix for bug 5460. more information in later
* comment about bug 5460.
*/
if (act == delete_act) {
// delete pass
if (s == COMMIT_STATE_DELETED || s == COMMIT_STATE_CHANGED) {
if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
node, delete_act, def)) {
return false;
}
}
} else {
// update pass
if (s == COMMIT_STATE_ADDED || s == COMMIT_STATE_CHANGED) {
if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
node, create_act, def)) {
return false;
}
}
}
// end
if (!_exec_tmpl_actions(cs, s, at_str.get(), pcomps, *(pdisp.get()),
node, 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 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) {
// check if an immediate child node has changed.
// if so do the syntax act here
// This puts back the pre-larkspur behavior that features expect to happen.
vector childNodes;
childNodes = nodelist[i]->getChildNodes();
for (size_t j = 0; j < childNodes.size(); j++){
if (childNodes[j]->getCommitState() != COMMIT_STATE_UNCHANGED) {
if (!_exec_node_actions(cs, *(nodelist[i]), syntax_act)) {
return false;
}
break; // break out of the inner for loop
}
}
continue; //continue the outer for loop
}
_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 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 values;
vector pfxs;
if (!cnode::cmp_multi_values(cfg1, cfg2, values, pfxs)) {
// no change
return NULL;
}
vector 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 if (pfxs[i] == DIFF_UPD) {
states.push_back(COMMIT_STATE_CHANGED);
} else {
/* note that previously this was following the original impl
* and treating both DIFF_UPD and DIFF_NONE as "unchanged".
* in other words, if a value's position among the multiple
* values changes, no action will be invoked during commit.
*
* however, this was the cause of bug 5460. if the ordering
* for a "multi:" node actually matters, this bug means the
* value ordering will not work correctly for a value whose
* position has changed but is not being added or deleted.
*
* to fix the bug, we now distinguish between UPD and NONE.
*/
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 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;
}
static void
_execute_hooks(CommitHook hook)
{
string cmd = "/bin/run-parts --regex='^[a-zA-Z0-9._-]+$' -- '";
cmd += getCommitHookDir(hook);
cmd += "'";
// not checking return status
restore_output();
if(system(cmd.c_str()));
redirect_output();
}
////// 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& values, const vector& 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 def)
{
_def = def;
}
tr1::shared_ptr
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(), _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
const char *
commit::getCommitHookDir(CommitHook hook)
{
if (hook > LAST) {
return NULL;
}
return commit_hook_dirs[hook];
}
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 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 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)
{
/* get the lock first.
* note: the getCommitLock() interface provided by Cstore guarantees
* that the lock will be released upon process termination (either
* normally or abnormally), so this is all that is required in terms
* of commit locking.
*/
if (!cs.getCommitLock()) {
OUTPUT_USER("Configuration system temporarily locked "
"due to another commit in progress\n");
return false;
}
Cpath p;
CfgNode *root = getCommitTree(&cfg1, &cfg2, p);
if (!root) {
/* "session changed" check has already been performed before commit
* execution, so no need to repeat it here.
*
* call the low-level commitConfig() function with a dummy structure
* representing successful commit on the whole tree. this is equivalent
* to copying the whole working config back to the active config. since
* the two are "logically" the same, this is redundant in most cases.
* however, in some cases, this gives the low-level implementation a
* chance to clean up any intermediate state that are no longer needed.
*/
Cpath rp;
#if __GNUC__ < 6
auto_ptr cn(new CfgNode(rp, NULL, NULL, NULL, 0, &cs, false));
#else
unique_ptr cn(new CfgNode(rp, NULL, NULL, NULL, 0, &cs, false));
#endif
PrioNode pn(cn.get());
pn.setSucceeded(true);
if (!cs.commitConfig(pn)) {
OUTPUT_USER("Failed to generate committed config\n");
return false;
}
return true;
}
_execute_hooks(PRE_COMMIT);
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
OUTPUT_USER("delete [ %s ] failed\n",
p->getCommitPath().to_string().c_str());
++f;
} else {
// succeeded
++s;
}
dpq.pop();
}
while (!pq.empty()) {
PrioNode *p = pq.top();
if (!_commit_exec_prio_subtree(cs, p)) {
// prio subtree failed
OUTPUT_USER("[[%s]] failed\n",
p->getCommitPath().to_string().c_str());
++f;
} else {
// succeeded
++s;
}
pq.pop();
}
bool ret = true;
const char *cst = "SUCCESS";
if (f > 0) {
OUTPUT_USER("Commit failed\n");
ret = false;
cst = ((s > 0) ? "PARTIAL" : "FAILURE");
}
if (s > 0) {
// notify other users in config mode
if(system("/opt/vyatta/sbin/vyatta-cfg-notify"));
}
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;
}
if (ret) {
ret = cs.markSessionUnsaved();
}
sync();
setenv("COMMIT_STATUS", cst, 1);
_execute_hooks(POST_COMMIT);
unsetenv("COMMIT_STATUS");
return ret;
}