summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAn-Cheng Huang <ancheng@vyatta.com>2011-04-30 21:42:12 +0800
committerAn-Cheng Huang <ancheng@vyatta.com>2011-05-10 09:25:13 +0800
commit491b4c361f3a612835e76604fbd751e6e6905c3d (patch)
tree0fdb2e86fab5938bf171d23ef7cf23ccd555e531 /src
parent4c5199a11c951361934c7c5d4bd91e7e2ae8679a (diff)
downloadvyatta-cfg-491b4c361f3a612835e76604fbd751e6e6905c3d.tar.gz
vyatta-cfg-491b4c361f3a612835e76604fbd751e6e6905c3d.zip
preliminary implementation of new commit
(cherry picked from commit 1b2a0fd1ae1e6dfc18e4f75f73cd7befb47cf538)
Diffstat (limited to 'src')
-rw-r--r--src/cli_bin.cpp35
-rw-r--r--src/cli_cstore.h7
-rw-r--r--src/cli_new.c24
-rw-r--r--src/cli_objects.h5
-rw-r--r--src/cli_shell_api.cpp1
-rw-r--r--src/cli_val.h4
-rw-r--r--src/cnode/cnode-algorithm.cpp64
-rw-r--r--src/cnode/cnode-algorithm.hpp53
-rw-r--r--src/cnode/cnode-util.hpp84
-rw-r--r--src/cnode/cnode.cpp40
-rw-r--r--src/cnode/cnode.hpp56
-rw-r--r--src/commit/commit-algorithm.cpp1191
-rw-r--r--src/commit/commit-algorithm.hpp196
-rw-r--r--src/cparse/cparse.hpp6
-rw-r--r--src/cparse/cparse.ypp2
-rw-r--r--src/cstore/cstore-varref.cpp40
-rw-r--r--src/cstore/cstore.cpp83
-rw-r--r--src/cstore/cstore.hpp26
-rw-r--r--src/cstore/ctemplate.hpp19
-rw-r--r--src/cstore/unionfs/cstore-unionfs.cpp495
-rw-r--r--src/cstore/unionfs/cstore-unionfs.hpp62
-rw-r--r--src/cstore/unionfs/fspath.hpp1
22 files changed, 2215 insertions, 279 deletions
diff --git a/src/cli_bin.cpp b/src/cli_bin.cpp
index 4d32c15..71c9fa9 100644
--- a/src/cli_bin.cpp
+++ b/src/cli_bin.cpp
@@ -23,6 +23,8 @@
#include <cli_cstore.h>
#include <cstore/cstore.hpp>
+#include <cnode/cnode.hpp>
+#include <commit/commit-algorithm.hpp>
using namespace cstore;
@@ -37,6 +39,7 @@ static const char *op_bin_name[] = {
"my_comment",
"my_discard",
"my_move",
+ "my_commit",
NULL
};
static const char *op_Str[] = {
@@ -49,6 +52,7 @@ static const char *op_Str[] = {
"Comment",
"Discard",
"Move",
+ "Commit",
NULL
};
static const char *op_str[] = {
@@ -61,6 +65,7 @@ static const char *op_str[] = {
"comment",
"discard",
"move",
+ "commit",
NULL
};
static const bool op_need_cfg_node_args[] = {
@@ -73,11 +78,26 @@ static const bool op_need_cfg_node_args[] = {
true,
false,
true,
+ false,
+ false // dummy
+};
+static const bool op_use_edit_level[] = {
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ false,
false // dummy
};
#define OP_Str op_Str[op_idx]
#define OP_str op_str[op_idx]
#define OP_need_cfg_node_args op_need_cfg_node_args[op_idx]
+#define OP_use_edit_level op_use_edit_level[op_idx]
static void
doSet(Cstore& cstore, const Cpath& path_comps)
@@ -173,6 +193,17 @@ doMove(Cstore& cstore, const Cpath& path_comps)
}
}
+static void
+doCommit(Cstore& cstore, const Cpath& path_comps)
+{
+ Cpath dummy;
+ cnode::CfgNode aroot(cstore, dummy, true, true);
+ cnode::CfgNode wroot(cstore, dummy, false, true);
+ if (!commit::doCommit(cstore, aroot, wroot)) {
+ exit(1);
+ }
+}
+
typedef void (*OpFuncT)(Cstore& cstore,
const Cpath& path_comps);
OpFuncT OpFunc[] = {
@@ -185,6 +216,7 @@ OpFuncT OpFunc[] = {
&doComment,
&doDiscard,
&doMove,
+ &doCommit,
NULL
};
@@ -212,8 +244,7 @@ main(int argc, char **argv)
bye("nothing to %s\n", OP_str);
}
- // actual CLI operations use the edit levels from environment, so pass true.
- Cstore *cstore = Cstore::createCstore(true);
+ Cstore *cstore = Cstore::createCstore(OP_use_edit_level);
Cpath path_comps(const_cast<const char **>(argv + 1), argc - 1);
// call the op function
diff --git a/src/cli_cstore.h b/src/cli_cstore.h
index 6f4ec51..c62f2e0 100644
--- a/src/cli_cstore.h
+++ b/src/cli_cstore.h
@@ -127,17 +127,22 @@ extern FILE *err_stream;
} while (0);
/* functions */
-const valstruct *get_syntax_self_in_valstruct(vtw_node *vnode);
+const valstruct *get_syntax_self_in_valstruct(const vtw_node *vnode);
int get_shell_command_output(const char *cmd, char *buf,
unsigned int buf_size);
int parse_def(vtw_def *defp, const char *path, boolean type_only);
boolean validate_value(const vtw_def *def, char *value);
+boolean execute_list(vtw_node *cur, const vtw_def *def, const char *outbuf,
+ boolean format);
const char *type_to_name(vtw_type_e type);
int initialize_output(const char *op);
void bye(const char *msg, ...) __attribute__((format(printf, 1, 2), noreturn));
/* functions from cli_objects */
char *get_at_string(void);
+void set_in_commit(boolean b);
+void set_at_string(char* s);
+void set_in_delete_action(boolean b);
#ifdef __cplusplus
}
diff --git a/src/cli_new.c b/src/cli_new.c
index 950eb79..30a028b 100644
--- a/src/cli_new.c
+++ b/src/cli_new.c
@@ -223,7 +223,7 @@ void append(vtw_list *l, vtw_node *n, int aux)
* completion mechanism to get such "allowed" values specified by "syntax".
*/
const valstruct *
-get_syntax_self_in_valstruct(vtw_node *vnode)
+get_syntax_self_in_valstruct(const vtw_node *vnode)
{
const valstruct *ret = NULL;
if (!vnode) {
@@ -427,7 +427,9 @@ get_config_lock()
/* returns FALSE if execution returns non-null,
returns TRUE if every excution returns NULL
*/
-boolean execute_list(vtw_node *cur, vtw_def *def, const char *prepend_msg,boolean format)
+boolean
+execute_list(vtw_node *cur, const vtw_def *def, const char *prepend_msg,
+ boolean format)
{
boolean ret;
int status;
@@ -912,11 +914,7 @@ static int change_var_value(const char* var_reference,const char* value, int act
if(var_reference && value) {
if (var_ref_handle) {
- /* XXX current var ref lib implementation is fs-specific.
- * for now treat it as a part of the unionfs-specific
- * cstore implementation.
- * handle is set => we are in cstore operation.
- */
+ /* handle is set => we are in cstore operation. */
if (cstore_set_var_ref(var_ref_handle, var_reference, value,
active_dir)) {
ret = 0;
@@ -1217,11 +1215,7 @@ static int eval_va(valstruct *res, vtw_node *node)
*endp = 0;
if (var_ref_handle) {
- /* XXX current var ref lib implementation is fs-specific.
- * for now treat it as a part of the unionfs-specific
- * cstore implementation.
- * handle is set => we are in cstore operation.
- */
+ /* handle is set => we are in cstore operation. */
vtw_type_e vtype;
char *vptr = NULL;
if (!cstore_get_var_ref(var_ref_handle, pathp, &vtype, &vptr,
@@ -1395,11 +1389,7 @@ static int expand_string(char *stringp)
bye("Empty path");
if (var_ref_handle) {
- /* XXX current var ref lib implementation is fs-specific.
- * for now treat it as a part of the unionfs-specific
- * cstore implementation.
- * handle is set => we are in cstore operation.
- */
+ /* handle is set => we are in cstore operation. */
vtw_type_e vtype;
char *vptr = NULL;
if (cstore_get_var_ref(var_ref_handle, scanp, &vtype, &vptr,
diff --git a/src/cli_objects.h b/src/cli_objects.h
index e9ae12e..8bbff64 100644
--- a/src/cli_objects.h
+++ b/src/cli_objects.h
@@ -21,15 +21,10 @@ extern "C" {
/* the string to use as $(@), must be set
before call to expand_string */
char* get_at_string(void);
-void set_at_string(char* s);
void free_at_string(void);
boolean is_in_delete_action(void);
-void set_in_delete_action(boolean b);
-
boolean is_in_commit(void);
-void set_in_commit(boolean b);
-
boolean is_in_exec(void);
void set_in_exec(boolean b);
diff --git a/src/cli_shell_api.cpp b/src/cli_shell_api.cpp
index 3ab4996..6e8f384 100644
--- a/src/cli_shell_api.cpp
+++ b/src/cli_shell_api.cpp
@@ -25,6 +25,7 @@
#include <cstore/cstore.hpp>
#include <cnode/cnode.hpp>
#include <cnode/cnode-algorithm.hpp>
+#include <commit/commit-algorithm.hpp>
#include <cparse/cparse.hpp>
using namespace cstore;
diff --git a/src/cli_val.h b/src/cli_val.h
index a3eb201..d3950c7 100644
--- a/src/cli_val.h
+++ b/src/cli_val.h
@@ -87,11 +87,7 @@ extern valstruct str2val(char *cp);
extern void switch_path(first_seg *seg);
extern void free_val(valstruct *val);
extern int mkdir_p(const char *path);
-
-extern boolean execute_list(vtw_node *cur, vtw_def *def, const char *outbuf,boolean format);
-
extern void free_path(vtw_path *path);
-
extern int get_config_lock(void);
extern void release_config_lock(void);
diff --git a/src/cnode/cnode-algorithm.cpp b/src/cnode/cnode-algorithm.cpp
index 607d5d2..6638936 100644
--- a/src/cnode/cnode-algorithm.cpp
+++ b/src/cnode/cnode-algorithm.cpp
@@ -25,6 +25,8 @@
#include <cnode/cnode-algorithm.hpp>
using namespace cnode;
+using namespace cstore;
+using namespace std;
////// constants
@@ -38,6 +40,24 @@ const string cnode::ACTIVE_CFG = "@ACTIVE";
const string cnode::WORKING_CFG = "@WORKING";
////// static (internal) functions
+static inline const char *
+diff_to_pfx(DiffState s)
+{
+ switch (s) {
+ case DIFF_ADD:
+ return PFX_DIFF_ADD.c_str();
+ case DIFF_DEL:
+ return PFX_DIFF_DEL.c_str();
+ case DIFF_UPD:
+ return PFX_DIFF_UPD.c_str();
+ case DIFF_NONE:
+ return PFX_DIFF_NONE.c_str();
+ case DIFF_NULL:
+ default:
+ return PFX_DIFF_NULL.c_str();
+ }
+}
+
static void
_show_diff(const CfgNode *cfg1, const CfgNode *cfg2, int level,
Cpath& cur_path, Cpath& last_ctx, bool show_def,
@@ -49,16 +69,16 @@ _get_cmds_diff(const CfgNode *cfg1, const CfgNode *cfg2,
vector<Cpath>& set_list, vector<Cpath>& com_list);
/* compare the values of a "multi" node in the two configs. the values and
- * the "prefix" of each value are returned in "values" and "pfxs",
+ * the "diff" of each value are returned in "values" and "pfxs",
* respectively.
*
* return value indicates whether the node is different in the two configs.
*
* comparison follows the original perl logic.
*/
-static bool
-_cmp_multi_values(const CfgNode *cfg1, const CfgNode *cfg2,
- vector<string>& values, vector<const char *>& pfxs)
+bool
+cnode::cmp_multi_values(const CfgNode *cfg1, const CfgNode *cfg2,
+ vector<string>& values, vector<DiffState>& pfxs)
{
const vector<string>& ovec = cfg1->getValues();
const vector<string>& nvec = cfg2->getValues();
@@ -72,7 +92,7 @@ _cmp_multi_values(const CfgNode *cfg1, const CfgNode *cfg2,
omap[ovec[i]] = true;
if (nmap.find(ovec[i]) == nmap.end()) {
values.push_back(ovec[i]);
- pfxs.push_back(PFX_DIFF_DEL.c_str());
+ pfxs.push_back(DIFF_DEL);
changed = true;
}
}
@@ -80,12 +100,12 @@ _cmp_multi_values(const CfgNode *cfg1, const CfgNode *cfg2,
for (size_t i = 0; i < nvec.size(); i++) {
values.push_back(nvec[i]);
if (omap.find(nvec[i]) == omap.end()) {
- pfxs.push_back(PFX_DIFF_ADD.c_str());
+ pfxs.push_back(DIFF_ADD);
changed = true;
} else if (i < ovec.size() && nvec[i] == ovec[i]) {
- pfxs.push_back(PFX_DIFF_NONE.c_str());
+ pfxs.push_back(DIFF_NONE);
} else {
- pfxs.push_back(PFX_DIFF_UPD.c_str());
+ pfxs.push_back(DIFF_UPD);
changed = true;
}
}
@@ -93,12 +113,12 @@ _cmp_multi_values(const CfgNode *cfg1, const CfgNode *cfg2,
return changed;
}
-static void
-_cmp_non_leaf_nodes(const CfgNode *cfg1, const CfgNode *cfg2,
- vector<CfgNode *>& rcnodes1,
- vector<CfgNode *>& rcnodes2, bool& not_tag_node,
- bool& is_value, bool& is_leaf_typeless,
- string& name, string& value)
+void
+cnode::cmp_non_leaf_nodes(const CfgNode *cfg1, const CfgNode *cfg2,
+ vector<CfgNode *>& rcnodes1,
+ vector<CfgNode *>& rcnodes2, bool& not_tag_node,
+ bool& is_value, bool& is_leaf_typeless,
+ string& name, string& value)
{
const CfgNode *cfg = (cfg1 ? cfg1 : cfg2);
is_value = cfg->isValue();
@@ -361,12 +381,12 @@ _diff_check_and_show_leaf(const CfgNode *cfg1, const CfgNode *cfg2, int level,
} else {
// need to actually do a diff.
vector<string> values;
- vector<const char *> pfxs;
- bool changed = _cmp_multi_values(cfg1, cfg2, values, pfxs);
+ vector<DiffState> pfxs;
+ bool changed = cmp_multi_values(cfg1, cfg2, values, pfxs);
if (!context_diff || changed) {
// not context diff OR there is a difference => print the node
for (size_t i = 0; i < values.size(); i++) {
- if (context_diff && pfxs[i] == PFX_DIFF_NONE.c_str()) {
+ if (context_diff && pfxs[i] == DIFF_NONE) {
// not printing unchanged values if doing context diff
continue;
}
@@ -380,7 +400,7 @@ _diff_check_and_show_leaf(const CfgNode *cfg1, const CfgNode *cfg2, int level,
_diff_print_context(cur_path, last_ctx);
cprint = true;
}
- _diff_print_indent(cfg1, cfg2, level, pfxs[i]);
+ _diff_print_indent(cfg1, cfg2, level, diff_to_pfx(pfxs[i]));
printf("%s ", cfg->getName().c_str());
_print_value_str(cfg->getName(), values[i].c_str(), hide_secret);
printf("\n");
@@ -441,7 +461,7 @@ _diff_show_other(const CfgNode *cfg1, const CfgNode *cfg2, int level,
string name, value;
bool not_tag_node, is_value, is_leaf_typeless;
vector<CfgNode *> rcnodes1, rcnodes2;
- _cmp_non_leaf_nodes(cfg1, cfg2, rcnodes1, rcnodes2, not_tag_node, is_value,
+ cmp_non_leaf_nodes(cfg1, cfg2, rcnodes1, rcnodes2, not_tag_node, is_value,
is_leaf_typeless, name, value);
/* only print "this" node if it
@@ -700,8 +720,8 @@ _get_cmds_diff_leaf(const CfgNode *cfg1, const CfgNode *cfg2,
} else {
// need to actually do a diff.
vector<string> dummy_vals;
- vector<const char *> dummy_pfxs;
- if (_cmp_multi_values(cfg1, cfg2, dummy_vals, dummy_pfxs)) {
+ vector<DiffState> dummy_pfxs;
+ if (cmp_multi_values(cfg1, cfg2, dummy_vals, dummy_pfxs)) {
/* something changed. to get the correct ordering for multi-node
* values, need to delete the node and then set the new values.
*/
@@ -755,7 +775,7 @@ _get_cmds_diff_other(const CfgNode *cfg1, const CfgNode *cfg2,
string name, value;
bool not_tag_node, is_value, is_leaf_typeless;
vector<CfgNode *> rcnodes1, rcnodes2;
- _cmp_non_leaf_nodes(cfg1, cfg2, rcnodes1, rcnodes2, not_tag_node, is_value,
+ cmp_non_leaf_nodes(cfg1, cfg2, rcnodes1, rcnodes2, not_tag_node, is_value,
is_leaf_typeless, name, value);
if (rcnodes1.size() < 1 && list) {
// subtree is empty
diff --git a/src/cnode/cnode-algorithm.hpp b/src/cnode/cnode-algorithm.hpp
index 547b55c..509f875 100644
--- a/src/cnode/cnode-algorithm.hpp
+++ b/src/cnode/cnode-algorithm.hpp
@@ -17,12 +17,34 @@
#ifndef _CNODE_ALGORITHM_HPP_
#define _CNODE_ALGORITHM_HPP_
+#include <vector>
+#include <string>
+
+#include <cstore/cpath.hpp>
#include <cnode/cnode.hpp>
namespace cnode {
+enum DiffState {
+ DIFF_ADD,
+ DIFF_DEL,
+ DIFF_UPD,
+ DIFF_NONE,
+ DIFF_NULL
+};
+
+bool cmp_multi_values(const CfgNode *cfg1, const CfgNode *cfg2,
+ std::vector<std::string>& values,
+ std::vector<DiffState>& pfxs);
+void cmp_non_leaf_nodes(const CfgNode *cfg1, const CfgNode *cfg2,
+ std::vector<CfgNode *>& rcnodes1,
+ std::vector<CfgNode *>& rcnodes2,
+ bool& not_tag_node, bool& is_value,
+ bool& is_leaf_typeless, std::string& name,
+ std::string& value);
+
void show_cfg_diff(const CfgNode& cfg1, const CfgNode& cfg2,
- Cpath& cur_path, bool show_def = false,
+ cstore::Cpath& cur_path, bool show_def = false,
bool hide_secret = false, bool context_diff = false);
void show_cfg(const CfgNode& cfg, bool show_def = false,
bool hide_secret = false);
@@ -31,24 +53,27 @@ void show_cmds_diff(const CfgNode& cfg1, const CfgNode& cfg2);
void show_cmds(const CfgNode& cfg);
void get_cmds_diff(const CfgNode& cfg1, const CfgNode& cfg2,
- vector<Cpath>& del_list, vector<Cpath>& set_list,
- vector<Cpath>& com_list);
-void get_cmds(const CfgNode& cfg, vector<Cpath>& set_list,
- vector<Cpath>& com_list);
+ std::vector<cstore::Cpath>& del_list,
+ std::vector<cstore::Cpath>& set_list,
+ std::vector<cstore::Cpath>& com_list);
+void get_cmds(const CfgNode& cfg, std::vector<cstore::Cpath>& set_list,
+ std::vector<cstore::Cpath>& com_list);
-extern const string ACTIVE_CFG;
-extern const string WORKING_CFG;
+extern const std::string ACTIVE_CFG;
+extern const std::string WORKING_CFG;
-void showConfig(const string& cfg1, const string& cfg2,
- const Cpath& path, bool show_def = false,
+void showConfig(const std::string& cfg1, const std::string& cfg2,
+ const cstore::Cpath& path, bool show_def = false,
bool hide_secret = false, bool context_diff = false,
bool show_cmds = false, bool ignore_edit = false);
-CfgNode *findCfgNode(CfgNode *root, const Cpath& path, bool& is_value);
-CfgNode *findCfgNode(CfgNode *root, const Cpath& path);
-bool getCfgNodeValue(CfgNode *root, const Cpath& path, string& value);
-bool getCfgNodeValues(CfgNode *root, const Cpath& path,
- vector<string>& values);
+CfgNode *findCfgNode(CfgNode *root, const cstore::Cpath& path,
+ bool& is_value);
+CfgNode *findCfgNode(CfgNode *root, const cstore::Cpath& path);
+bool getCfgNodeValue(CfgNode *root, const cstore::Cpath& path,
+ std::string& value);
+bool getCfgNodeValues(CfgNode *root, const cstore::Cpath& path,
+ std::vector<std::string>& values);
} // namespace cnode
diff --git a/src/cnode/cnode-util.hpp b/src/cnode/cnode-util.hpp
new file mode 100644
index 0000000..aeeeb08
--- /dev/null
+++ b/src/cnode/cnode-util.hpp
@@ -0,0 +1,84 @@
+/*
+ * 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/>.
+ */
+
+#ifndef _CNODE_UTIL_HPP_
+#define _CNODE_UTIL_HPP_
+#include <vector>
+
+namespace cnode {
+
+template<class N> class TreeNode {
+public:
+ typedef N node_type;
+ typedef std::vector<N *> nodes_vec_type;
+ typedef typename nodes_vec_type::iterator nodes_iter_type;
+
+ TreeNode() : _parent(0) {}
+ virtual ~TreeNode() {}
+
+ const nodes_vec_type& getChildNodes() const { return _child_nodes; }
+ size_t numChildNodes() const {
+ return _child_nodes.size();
+ }
+ node_type *getParent() const { return _parent; }
+ node_type *childAt(size_t idx) { return _child_nodes[idx]; }
+ void setParent(node_type *p) { _parent = p; }
+ void clearChildNodes() { _child_nodes.clear(); }
+ void addChildNode(node_type *cnode) {
+ _child_nodes.push_back(cnode);
+ cnode->_parent = static_cast<node_type *>(this);
+ }
+
+ bool removeChildNode(node_type *cnode) {
+ nodes_iter_type it = _child_nodes.begin();
+ while (it != _child_nodes.end()) {
+ if (*it == cnode) {
+ _child_nodes.erase(it);
+ return true;
+ }
+ ++it;
+ }
+ return false;
+ }
+
+ bool detachFromParent() {
+ if (!_parent) {
+ // not attached to tree
+ return false;
+ }
+ if (_parent->removeChildNode(static_cast<node_type *>(this))) {
+ _parent = 0;
+ return true;
+ }
+ return false;
+ }
+
+ void detachFromChildren() {
+ for (size_t i = 0; i < _child_nodes.size(); i++) {
+ _child_nodes[i]->_parent = 0;
+ }
+ _child_nodes.clear();
+ }
+
+private:
+ node_type *_parent;
+ nodes_vec_type _child_nodes;
+};
+
+} // namespace cnode
+
+#endif /* _CNODE_UTIL_HPP_ */
+
diff --git a/src/cnode/cnode.cpp b/src/cnode/cnode.cpp
index cd4f6cf..279ee9a 100644
--- a/src/cnode/cnode.cpp
+++ b/src/cnode/cnode.cpp
@@ -25,14 +25,17 @@
#include <cnode/cnode.hpp>
using namespace cnode;
+using namespace cstore;
////// constructors/destructors
+// for parser
CfgNode::CfgNode(Cpath& path_comps, char *name, char *val, char *comment,
int deact, Cstore *cstore, bool tag_if_invalid)
- : _is_tag(false), _is_leaf(false), _is_multi(false), _is_value(false),
+ : TreeNode<CfgNode>(),
+ _is_tag(false), _is_leaf(false), _is_multi(false), _is_value(false),
_is_default(false), _is_deactivated(false), _is_leaf_typeless(false),
- _is_invalid(false), _is_empty(true), _exists(true)
+ _is_invalid(false), _exists(true)
{
if (name && name[0]) {
// name must be non-empty
@@ -49,15 +52,15 @@ CfgNode::CfgNode(Cpath& path_comps, char *name, char *val, char *comment,
break;
}
- tr1::shared_ptr<Ctemplate> def(cstore->parseTmpl(path_comps, false));
- if (def.get()) {
+ setTmpl(cstore->parseTmpl(path_comps, false));
+ if (getTmpl().get()) {
// got the def
- _is_tag = def->isTag();
- _is_leaf = (!_is_tag && !def->isTypeless());
+ _is_tag = getTmpl()->isTag();
+ _is_leaf = (!_is_tag && !getTmpl()->isTypeless());
// match constructor from cstore (leaf node never _is_value)
- _is_value = (def->isValue() && !_is_leaf);
- _is_multi = def->isMulti();
+ _is_value = (getTmpl()->isValue() && !_is_leaf);
+ _is_multi = getTmpl()->isMulti();
/* XXX given the current definition of "default", the concept of
* "default" doesn't really apply to config files.
@@ -113,18 +116,20 @@ CfgNode::CfgNode(Cpath& path_comps, char *name, char *val, char *comment,
}
}
+// for active/working config
CfgNode::CfgNode(Cstore& cstore, Cpath& path_comps, bool active,
bool recursive)
- : _is_tag(false), _is_leaf(false), _is_multi(false), _is_value(false),
+ : TreeNode<CfgNode>(),
+ _is_tag(false), _is_leaf(false), _is_multi(false), _is_value(false),
_is_default(false), _is_deactivated(false), _is_leaf_typeless(false),
- _is_invalid(false), _is_empty(false), _exists(true)
+ _is_invalid(false), _exists(true)
{
/* first get the def (only if path is not empty). if path is empty, i.e.,
* "root", treat it as an intermediate node.
*/
if (path_comps.size() > 0) {
- tr1::shared_ptr<Ctemplate> def(cstore.parseTmpl(path_comps, false));
- if (def.get()) {
+ setTmpl(cstore.parseTmpl(path_comps, false));
+ if (getTmpl().get()) {
// got the def
if (!cstore.cfgPathExists(path_comps, active)) {
// path doesn't exist
@@ -132,10 +137,10 @@ CfgNode::CfgNode(Cstore& cstore, Cpath& path_comps, bool active,
return;
}
- _is_value = def->isValue();
- _is_tag = def->isTag();
- _is_leaf = (!_is_tag && !def->isTypeless());
- _is_multi = def->isMulti();
+ _is_value = getTmpl()->isValue();
+ _is_tag = getTmpl()->isTag();
+ _is_leaf = (!_is_tag && !getTmpl()->isTypeless());
+ _is_multi = getTmpl()->isMulti();
_is_default = cstore.cfgPathDefault(path_comps, active);
_is_deactivated = cstore.cfgPathDeactivated(path_comps, active);
cstore.cfgPathGetComment(path_comps, _comment, active);
@@ -190,7 +195,6 @@ CfgNode::CfgNode(Cstore& cstore, Cpath& path_comps, bool active,
// typeless leaf node
_is_leaf_typeless = true;
}
- _is_empty = true;
return;
}
@@ -203,7 +207,7 @@ CfgNode::CfgNode(Cstore& cstore, Cpath& path_comps, bool active,
for (size_t i = 0; i < cnodes.size(); i++) {
path_comps.push(cnodes[i]);
CfgNode *cn = new CfgNode(cstore, path_comps, active, recursive);
- _child_nodes.push_back(cn);
+ addChildNode(cn);
path_comps.pop();
}
}
diff --git a/src/cnode/cnode.hpp b/src/cnode/cnode.hpp
index a684514..2d38737 100644
--- a/src/cnode/cnode.hpp
+++ b/src/cnode/cnode.hpp
@@ -16,24 +16,29 @@
#ifndef _CNODE_HPP_
#define _CNODE_HPP_
+#include <cstdio>
#include <vector>
#include <string>
#include <cstore/cstore.hpp>
+#include <cnode/cnode-util.hpp>
+#include <commit/commit-algorithm.hpp>
namespace cnode {
-using namespace cstore;
-
-class CfgNode {
+class CfgNode : public TreeNode<CfgNode>, public commit::CommitData {
public:
- CfgNode(Cpath& path_comps, char *name, char *val, char *comment,
- int deact, Cstore *cstore, bool tag_if_invalid = false);
- CfgNode(Cstore& cstore, Cpath& path_comps, bool active = false,
- bool recursive = true);
+ // constructor for parser
+ CfgNode(cstore::Cpath& path_comps, char *name, char *val, char *comment,
+ int deact, cstore::Cstore *cstore, bool tag_if_invalid = false);
+ // constructor for active/working config
+ CfgNode(cstore::Cstore& cstore, cstore::Cpath& path_comps,
+ bool active = false, bool recursive = true);
+
~CfgNode() {};
bool isTag() const { return _is_tag; }
+ bool isTagNode() const { return (_is_tag && !_is_value); }
bool isLeaf() const { return _is_leaf; }
bool isMulti() const { return _is_multi; }
bool isValue() const { return _is_value; }
@@ -41,22 +46,29 @@ public:
bool isDeactivated() const { return _is_deactivated; }
bool isLeafTypeless() const { return _is_leaf_typeless; }
bool isInvalid() const { return _is_invalid; }
- bool isEmpty() const { return _is_empty; }
+ bool isEmpty() const { return (!_is_leaf && numChildNodes() == 0); }
bool exists() const { return _exists; }
- const string& getName() const { return _name; }
- const string& getValue() const { return _value; }
- const vector<string>& getValues() const { return _values; }
- const string& getComment() const { return _comment; }
- const vector<CfgNode *>& getChildNodes() const { return _child_nodes; }
+ const std::string& getName() const { return _name; }
+ const std::string& getValue() const { return _value; }
+ const std::vector<std::string>& getValues() const { return _values; }
+ const std::string& getComment() const { return _comment; }
void addMultiValue(char *val) { _values.push_back(val); }
- void addChildNode(CfgNode *cnode) {
- _child_nodes.push_back(cnode);
- _is_empty = false;
- }
void setValue(char *val) { _value = val; }
+ // XXX testing
+ void rprint(size_t lvl) {
+ for (size_t i = 0; i < lvl; i++) {
+ printf(" ");
+ }
+ printf("[%u][%s][%d]\n", getPriority(),
+ getCommitPath().to_string().c_str(), getCommitState());
+ for (size_t i = 0; i < numChildNodes(); i++) {
+ childAt(i)->rprint(lvl + 1);
+ }
+ }
+
private:
bool _is_tag;
bool _is_leaf;
@@ -66,13 +78,11 @@ private:
bool _is_deactivated;
bool _is_leaf_typeless;
bool _is_invalid;
- bool _is_empty;
bool _exists;
- string _name;
- string _value;
- vector<string> _values;
- string _comment;
- vector<CfgNode *> _child_nodes;
+ std::string _name;
+ std::string _value;
+ std::vector<std::string> _values;
+ std::string _comment;
};
} // namespace cnode
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;
+}
+
diff --git a/src/commit/commit-algorithm.hpp b/src/commit/commit-algorithm.hpp
new file mode 100644
index 0000000..3bc89a7
--- /dev/null
+++ b/src/commit/commit-algorithm.hpp
@@ -0,0 +1,196 @@
+/*
+ * 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/>.
+ */
+
+#ifndef _COMMIT_ALGORITHM_HPP_
+#define _COMMIT_ALGORITHM_HPP_
+#include <vector>
+#include <string>
+#include <queue>
+#include <tr1/memory>
+
+#include <cnode/cnode-util.hpp>
+#include <cstore/cpath.hpp>
+#include <cstore/ctemplate.hpp>
+
+// forward decl
+namespace cnode {
+class CfgNode;
+}
+namespace cstore {
+class Cstore;
+}
+
+namespace commit {
+
+using namespace cnode;
+using namespace cstore;
+
+enum CommitState {
+ COMMIT_STATE_UNCHANGED,
+ COMMIT_STATE_ADDED,
+ COMMIT_STATE_DELETED,
+ COMMIT_STATE_CHANGED
+};
+
+enum CommitTreeTraversalOrder {
+ PRE_ORDER,
+ POST_ORDER
+};
+
+class CommitData {
+public:
+ CommitData();
+ virtual ~CommitData() {}
+
+ // setters
+ void setCommitState(CommitState s);
+ void setCommitPath(const Cpath& p, bool is_val, const std::string& val,
+ const std::string& name);
+ void setCommitMultiValues(const std::vector<std::string>& values,
+ const std::vector<CommitState>& states);
+ void setCommitValue(const std::string& val1, const std::string& val2,
+ bool def1, bool def2);
+ void setCommitChildDeleteFailed();
+ void setCommitCreateFailed();
+ void setCommitSubtreeChanged();
+
+ // getters
+ CommitState getCommitState() const;
+ Cpath getCommitPath() const;
+ size_t numCommitMultiValues() const;
+ std::string commitMultiValueAt(size_t idx) const;
+ CommitState commitMultiStateAt(size_t idx) const;
+ std::string commitValueBefore() const;
+ std::string commitValueAfter() const;
+ bool commitChildDeleteFailed() const;
+ bool commitCreateFailed() const;
+ bool commitSubtreeChanged() const;
+
+ // for tmpl stuff
+ void setTmpl(std::tr1::shared_ptr<cstore::Ctemplate> def);
+ std::tr1::shared_ptr<cstore::Ctemplate> getTmpl() const;
+ const vtw_def *getDef() const;
+ unsigned int getPriority() const;
+ void setPriority(unsigned int p);
+ const vtw_node *getActions(vtw_act_type act, bool raw = false) const;
+ bool isBeginEndNode() const;
+
+private:
+ std::tr1::shared_ptr<cstore::Ctemplate> _def;
+ Cpath _commit_path;
+ CommitState _commit_state;
+ std::vector<std::string> _commit_values;
+ std::vector<CommitState> _commit_values_states;
+ std::pair<std::string, std::string> _commit_value;
+ std::pair<bool, bool> _commit_default;
+ bool _commit_create_failed;
+ bool _commit_child_delete_failed;
+ bool _commit_subtree_changed;
+};
+
+class PrioNode : public TreeNode<PrioNode> {
+public:
+ PrioNode(CfgNode *n);
+ ~PrioNode() {}
+
+ CfgNode *getCfgNode();
+ unsigned int getPriority() const;
+ CommitState getCommitState() const;
+ Cpath getCommitPath() const;
+ bool parentCreateFailed() const;
+ bool succeeded() const;
+ bool hasSubtreeFailure() const;
+ bool hasSubtreeSuccess() const;
+
+ void setCfgParent(CfgNode *p);
+ void setSucceeded(bool succeeded);
+ void setSubtreeFailure();
+ void setSubtreeSuccess();
+
+#if 0
+ // XXX testing
+ void print(size_t lvl) {
+#define INDENT for(size_t xyz = 0; xyz < lvl; xyz++) { printf(" "); };
+ INDENT;
+ if (_node) {
+ printf("[%s]\n", _node->getCommitPath().to_string().c_str());
+ } else {
+ printf("[---]\n");
+ }
+ INDENT; printf("|cp[%s]\n",
+ (_cfg_parent
+ ? _cfg_parent->getCommitPath().to_string().c_str()
+ : NULL));
+ if (_node) {
+ INDENT; printf("======\n");
+ _node->rprint(lvl);
+ INDENT; printf("======\n");
+ }
+#undef INDENT
+ }
+ void rprint(size_t lvl) {
+ print(lvl);
+ for (size_t i = 0; i < numChildNodes(); i++) {
+ childAt(i)->rprint(lvl + 1);
+ }
+ }
+#endif
+
+private:
+ CfgNode *_node;
+ CfgNode *_cfg_parent;
+ bool _succeeded;
+ bool _subtree_failure;
+ bool _subtree_success;
+};
+
+template<bool for_delete> struct PrioNodeCmp {
+ inline bool operator()(PrioNode *a, PrioNode *b) {
+ return _is_after(a, b, Int2Type<for_delete>());
+ }
+
+ /* note: if comparing "for delete", use "<". if not for delete, use ">".
+ * if two nodes have the same priority, the ordering between them
+ * is not defined, i.e., can be either.
+ */
+ inline bool _is_after(PrioNode *a, PrioNode *b, Int2Type<false>) {
+ return (a->getPriority() > b->getPriority());
+ }
+ inline bool _is_after(PrioNode *a, PrioNode *b, Int2Type<true>) {
+ return (a->getPriority() < b->getPriority());
+ }
+};
+
+typedef std::priority_queue<PrioNode *, std::vector<PrioNode *>,
+ PrioNodeCmp<false> > PrioQueueT;
+typedef std::priority_queue<PrioNode *, std::vector<PrioNode *>,
+ PrioNodeCmp<true> > DelPrioQueueT;
+
+typedef std::pair<CommitState, std::tr1::shared_ptr<Cpath> >
+ CommittedPathT;
+typedef std::vector<CommittedPathT> CommittedPathListT;
+
+// exported functions
+CfgNode *getCommitTree(CfgNode *cfg1, CfgNode *cfg2, const Cpath& cur_path);
+bool isCommitPathEffective(Cstore& cs, const Cpath& pcomps,
+ std::tr1::shared_ptr<Ctemplate> def,
+ bool in_active, bool in_working);
+bool doCommit(Cstore& cs, CfgNode& cfg1, CfgNode& cfg2);
+
+} // namespace commit
+
+#endif /* _COMMIT_ALGORITHM_HPP_ */
+
diff --git a/src/cparse/cparse.hpp b/src/cparse/cparse.hpp
index c80562a..289813e 100644
--- a/src/cparse/cparse.hpp
+++ b/src/cparse/cparse.hpp
@@ -22,10 +22,8 @@
namespace cparse {
-using namespace cnode;
-
-CfgNode *parse_file(FILE *fin, Cstore& cs);
-CfgNode *parse_file(const char *fname, Cstore& cs);
+cnode::CfgNode *parse_file(FILE *fin, cstore::Cstore& cs);
+cnode::CfgNode *parse_file(const char *fname, cstore::Cstore& cs);
} // namespace cparse
diff --git a/src/cparse/cparse.ypp b/src/cparse/cparse.ypp
index 3b995aa..e70f09a 100644
--- a/src/cparse/cparse.ypp
+++ b/src/cparse/cparse.ypp
@@ -8,6 +8,8 @@
#include "cparse.hpp"
#include "cparse_def.h"
+using namespace cstore;
+using namespace cnode;
using namespace cparse;
/* to enable tracing, define ENABLE_PARSER_TRACE. may also want to invoke
diff --git a/src/cstore/cstore-varref.cpp b/src/cstore/cstore-varref.cpp
index 1e3be90..7549834 100644
--- a/src/cstore/cstore-varref.cpp
+++ b/src/cstore/cstore-varref.cpp
@@ -122,14 +122,12 @@ Cstore::VarRef::process_ref(const Cpath& ref_comps,
return;
}
}
- if (pcomps.size() < _orig_path_comps.size()) {
- // within the original path. @ translates to the path comp.
- pcomps.push(_orig_path_comps[pcomps.size()]);
- process_ref(rcomps, pcomps, def->getType(1));
- return;
- }
- if (def->isValue() || def->isTag()) {
- // invalid ref
+ if (!def->isSingleLeafNode() && !def->isMultiLeafNode()) {
+ if (pcomps.size() < _orig_path_comps.size()) {
+ // within the original path. @ translates to the path comp.
+ pcomps.push(_orig_path_comps[pcomps.size()]);
+ process_ref(rcomps, pcomps, def->getType(1));
+ }
return;
}
// handle leaf node
@@ -186,12 +184,16 @@ Cstore::VarRef::process_ref(const Cpath& ref_comps,
// just text. go down 1 level.
if (got_tmpl && def->isTagNode()) {
// at "tag node". need to go down 1 more level.
- if (pcomps.size() >= _orig_path_comps.size()) {
+ if (pcomps.size() > _orig_path_comps.size()) {
// already under the original node. invalid ref.
return;
+ } else if (pcomps.size() == _orig_path_comps.size()) {
+ // at the tag value. use the at_string.
+ pcomps.push(_at_string);
+ } else {
+ // within the original path. take the original tag value.
+ pcomps.push(_orig_path_comps[pcomps.size()]);
}
- // within the original path. take the original tag value.
- pcomps.push(_orig_path_comps[pcomps.size()]);
}
pcomps.push(cr_comp);
process_ref(rcomps, pcomps, ERROR_TYPE);
@@ -218,11 +220,16 @@ Cstore::VarRef::process_ref(const Cpath& ref_comps,
} else {
// single-value node
string val;
+ vtw_type_e t = def->getType(1);
if (!_cstore->cfgPathGetValue(pcomps, val, _active)) {
- return;
+ /* can't get value => treat it as non-existent (empty value
+ * and type ERROR_TYPE)
+ */
+ val = "";
+ t = ERROR_TYPE;
}
pcomps.push(val);
- _paths.push_back(pair<Cpath, vtw_type_e>(pcomps, def->getType(1)));
+ _paths.push_back(pair<Cpath, vtw_type_e>(pcomps, t));
// at leaf. stop recursion.
}
}
@@ -288,6 +295,13 @@ Cstore::VarRef::getSetPath(Cpath& path_comps)
return false;
}
path_comps = _paths[0].first;
+ /* note that for "varref set" operation, the varref must refer to the
+ * "value" of a single-value leaf node, e.g.,
+ * "$VAR(plaintext-password/@)". so pop the last comp to give the
+ * correct path for "set". the caller is responsible for verifying
+ * whether the path points to a single-value leaf node.
+ */
+ path_comps.pop();
return true;
}
diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp
index 143e501..b549649 100644
--- a/src/cstore/cstore.cpp
+++ b/src/cstore/cstore.cpp
@@ -36,9 +36,12 @@
#include <cnode/cnode.hpp>
#include <cnode/cnode-algorithm.hpp>
#include <cparse/cparse.hpp>
+#include <commit/commit-algorithm.hpp>
namespace cstore { // begin namespace cstore
+using namespace cnode;
+
////// constants
//// node status
const string Cstore::C_NODE_STATUS_DELETED = "deleted";
@@ -707,10 +710,10 @@ Cstore::getCompletionEnv(const Cpath& comps, string& env)
* shell into an array of values.
*/
free(buf);
- } else if (def->getActions(syntax_act)->vtw_list_head) {
+ } else if (def->getActions(syntax_act)) {
// look for "self ref in values" from syntax
- const valstruct *vals = get_syntax_self_in_valstruct(
- def->getActions(syntax_act)->vtw_list_head);
+ const valstruct *vals
+ = get_syntax_self_in_valstruct(def->getActions(syntax_act));
if (vals) {
if (vals->cnt == 0 && vals->val) {
comp_vals.push_back(vals->val);
@@ -1534,21 +1537,8 @@ Cstore::cfgPathEffective(const Cpath& path_comps)
}
bool in_work = cfg_path_exists(path_comps, false, false);
- if (in_active && in_work) {
- // case (1)
- return true;
- }
-
- auto_ptr<SavePaths> save(create_save_paths());
- append_cfg_path(path_comps);
- if (!in_active && in_work) {
- // check if case (2)
- return marked_committed(def, true);
- } else if (in_active && !in_work) {
- // check if case (3)
- return !marked_committed(def, false);
- }
- return false;
+ return commit::isCommitPathEffective(*this, path_comps, def,
+ in_active, in_work);
}
/* get names of "effective" child nodes of specified path during commit
@@ -1797,7 +1787,7 @@ Cstore::loadFile(const char *filename)
}
// get the config tree from the file
- cnode::CfgNode *froot = cparse::parse_file(fin, *this);
+ CfgNode *froot = cparse::parse_file(fin, *this);
if (!froot) {
output_user("Failed to parse specified config file\n");
return false;
@@ -1805,13 +1795,13 @@ Cstore::loadFile(const char *filename)
// get the config tree from the active config
Cpath args;
- cnode::CfgNode aroot(*this, args, true, true);
+ CfgNode aroot(*this, args, true, true);
// get the "commands diff" between the two
vector<Cpath> del_list;
vector<Cpath> set_list;
vector<Cpath> com_list;
- cnode::get_cmds_diff(aroot, *froot, del_list, set_list, com_list);
+ get_cmds_diff(aroot, *froot, del_list, set_list, com_list);
// "apply" the changes to the working config
for (size_t i = 0; i < del_list.size(); i++) {
@@ -1890,6 +1880,45 @@ Cstore::unmarkCfgPathChanged(const Cpath& path_comps)
return unmark_changed_with_descendants();
}
+// execute the specified actions
+bool
+Cstore::executeTmplActions(char *at_str, const Cpath& path,
+ const Cpath& disp_path, const vtw_node *actions,
+ const vtw_def *def)
+{
+ string sdisp = " ";
+ sdisp += disp_path.to_string();
+ sdisp += " ";
+ set_at_string(at_str);
+
+ auto_ptr<SavePaths> save(create_save_paths());
+ append_cfg_path(path);
+ append_tmpl_path(path);
+
+ var_ref_handle = (void *) this;
+ // const_cast for legacy code
+ bool ret = execute_list(const_cast<vtw_node *>(actions), def,
+ sdisp.c_str(), false);
+ var_ref_handle = NULL;
+ return ret;
+}
+
+bool
+Cstore::cfgPathMarkedCommitted(const Cpath& path_comps, bool is_delete)
+{
+ auto_ptr<SavePaths> save(create_save_paths());
+ append_cfg_path(path_comps);
+ return marked_committed(is_delete);
+}
+
+bool
+Cstore::markCfgPathCommitted(const Cpath& path_comps, bool is_delete)
+{
+ auto_ptr<SavePaths> save(create_save_paths());
+ append_cfg_path(path_comps);
+ return mark_committed(is_delete);
+}
+
////// protected functions
Cstore::SavePaths::~SavePaths() {
@@ -2665,9 +2694,15 @@ Cstore::validate_val(const tr1::shared_ptr<Ctemplate>& def, const char *value)
}
// validate_value() may change "value". make a copy first.
- char *vbuf = strdup(value);
- bool ret = validate_val_impl(def, vbuf);
- free(vbuf);
+ auto_ptr<char> vbuf(strdup(value));
+
+ /* set the handle to be used during validate_value() for var ref
+ * processing. this is a global var in cli_new.c.
+ */
+ var_ref_handle = (void *) this;
+ bool ret = validate_value(def->getDef(), vbuf.get());
+ var_ref_handle = NULL;
+
return ret;
}
diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp
index f2163a8..af4a56d 100644
--- a/src/cstore/cstore.hpp
+++ b/src/cstore/cstore.hpp
@@ -40,6 +40,13 @@ extern "C" void* Perl_get_context(void)
"calling %s() without config session", \
__func__);
+// forward decl
+namespace cnode {
+class CfgNode;
+}
+namespace commit {
+class PrioNode;
+}
namespace cstore { // begin namespace cstore
@@ -163,6 +170,13 @@ public:
virtual bool inSession() = 0;
// commit
bool unmarkCfgPathChanged(const Cpath& path_comps);
+ bool executeTmplActions(char *at_str, const Cpath& path,
+ const Cpath& disp_path, const vtw_node *actions,
+ const vtw_def *def);
+ bool cfgPathMarkedCommitted(const Cpath& path_comps, bool is_delete);
+ bool markCfgPathCommitted(const Cpath& path_comps, bool is_delete);
+ virtual bool clearCommittedMarkers() = 0;
+ virtual bool commitConfig(commit::PrioNode& pnode) = 0;
// load
bool loadFile(const char *filename);
@@ -361,14 +375,6 @@ private:
virtual bool get_comment(string& comment, bool active_cfg) = 0;
virtual bool marked_display_default(bool active_cfg) = 0;
- // observers during commit operation
- virtual bool marked_committed(const tr1::shared_ptr<Ctemplate>& def,
- bool is_set) = 0;
-
- // these operate on both current tmpl and work paths
- virtual bool validate_val_impl(const tr1::shared_ptr<Ctemplate>& def,
- char *value) = 0;
-
// observers for "edit/tmpl levels" (for "edit"-related operations)
/* note that these should be handled in the base class since they
* should not be implementation-specific. however, current definitions
@@ -385,6 +391,10 @@ private:
virtual void get_edit_level(Cpath& path_comps) = 0;
virtual bool edit_level_at_root() = 0;
+ // functions for commit operation
+ virtual bool marked_committed(bool is_delete) = 0;
+ virtual bool mark_committed(bool is_delete) = 0;
+
// these are for testing/debugging
virtual string cfg_path_to_str() = 0;
virtual string tmpl_path_to_str() = 0;
diff --git a/src/cstore/ctemplate.hpp b/src/cstore/ctemplate.hpp
index ebe4a5b..96c47b5 100644
--- a/src/cstore/ctemplate.hpp
+++ b/src/cstore/ctemplate.hpp
@@ -20,15 +20,13 @@
#include <tr1/memory>
#include <cli_cstore.h>
-#include <cstore/cstore.hpp>
namespace cstore { // begin namespace cstore
-using namespace std;
-
class Ctemplate {
public:
- Ctemplate(tr1::shared_ptr<vtw_def> def) : _def(def), _is_value(false) {};
+ Ctemplate(std::tr1::shared_ptr<vtw_def> def)
+ : _def(def), _is_value(false) {};
~Ctemplate() {};
bool isValue() const { return _is_value; };
@@ -82,8 +80,8 @@ public:
const char *getNodeHelp() const { return _def->def_node_help; };
const char *getEnumeration() const { return _def->def_enumeration; };
const char *getAllowed() const { return _def->def_allowed; };
- const vtw_list *getActions(vtw_act_type act) const {
- return &(_def->actions[act]);
+ const vtw_node *getActions(vtw_act_type act) const {
+ return _def->actions[act].vtw_list_head;
};
const char *getCompHelp() const { return _def->def_comp_help; };
const char *getValHelp() const { return _def->def_val_help; };
@@ -92,6 +90,13 @@ public:
unsigned int getPriority() const { return _def->def_priority; };
void setIsValue(bool is_val) { _is_value = is_val; };
+ void setPriority(unsigned int p) const {
+ /* this changes the parsed template and is only used during commit IF the
+ * priority specified in the template violates the "hierarchical
+ * constraint" and therefore needs to be changed.
+ */
+ _def->def_priority = p;
+ }
const vtw_def *getDef() const {
/* XXX this is a hack for code that has not been converted and is still
@@ -118,7 +123,7 @@ private:
* suitable containers so that memory allocation/deallocation can be
* handled properly.
*/
- tr1::shared_ptr<vtw_def> _def;
+ std::tr1::shared_ptr<vtw_def> _def;
bool _is_value; /* whether the last path component is a "value". set by
* the cstore in get_parsed_tmpl().
*/
diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp
index e1a7dfd..659a985 100644
--- a/src/cstore/unionfs/cstore-unionfs.cpp
+++ b/src/cstore/unionfs/cstore-unionfs.cpp
@@ -25,6 +25,8 @@
#include <cli_cstore.h>
#include <cstore/unionfs/cstore-unionfs.hpp>
+#include <cnode/cnode.hpp>
+#include <commit/commit-algorithm.hpp>
namespace cstore { // begin namespace cstore
namespace unionfs { // begin namespace unionfs
@@ -56,7 +58,7 @@ const string UnionfsCstore::C_MARKER_DEF_VALUE = "def";
const string UnionfsCstore::C_MARKER_DEACTIVATE = ".disable";
const string UnionfsCstore::C_MARKER_CHANGED = ".modified";
const string UnionfsCstore::C_MARKER_UNSAVED = ".unsaved";
-const string UnionfsCstore::C_COMMITTED_MARKER_FILE = "/tmp/.changes";
+const string UnionfsCstore::C_COMMITTED_MARKER_FILE = ".changes";
const string UnionfsCstore::C_COMMENT_FILE = ".comment";
const string UnionfsCstore::C_TAG_NAME = "node.tag";
const string UnionfsCstore::C_VAL_NAME = "node.val";
@@ -181,6 +183,7 @@ UnionfsCstore::UnionfsCstore(bool use_edit_level)
}
if ((val = getenv(C_ENV_TMP_ROOT.c_str()))) {
tmp_root = val;
+ init_commit_data();
}
if ((val = getenv(C_ENV_ACTIVE_ROOT.c_str()))) {
active_root = val;
@@ -240,6 +243,7 @@ UnionfsCstore::UnionfsCstore(const string& sid, string& env)
work_root = (C_DEF_WORK_PREFIX + sid);
change_root = (C_DEF_CHANGE_PREFIX + sid);
tmp_root = (C_DEF_TMP_PREFIX + sid);
+ init_commit_data();
string declr = " declare -x -r "; // readonly vars
env += " umask 002; {";
@@ -345,15 +349,7 @@ UnionfsCstore::setupSession()
}
// union mount
- string mopts = "dirs=";
- mopts += change_root.path_cstr();
- mopts += "=rw:";
- mopts += active_root.path_cstr();
- mopts += "=ro";
- if (mount("unionfs", work_root.path_cstr(), "unionfs", 0,
- mopts.c_str()) != 0) {
- output_internal("setup session mount failed [%s][%s]\n",
- strerror(errno), work_root.path_cstr());
+ if (!do_mount(change_root, active_root, work_root)) {
return false;
}
} else if (!path_is_directory(work_root)) {
@@ -381,9 +377,7 @@ UnionfsCstore::teardownSession()
}
// unmount the work root (union)
- if (umount(wstr.c_str()) != 0) {
- output_internal("teardown session umount failed [%s][%s]\n",
- strerror(errno), wstr.c_str());
+ if (!do_umount(work_root)) {
return false;
}
@@ -415,6 +409,275 @@ UnionfsCstore::inSession()
&& path_exists(work_root) && path_is_directory(work_root));
}
+bool
+UnionfsCstore::clearCommittedMarkers()
+{
+ try {
+ b_fs::remove(commit_marker_file.path_cstr());
+ } catch (...) {
+ output_internal("failed to clear committed markers\n");
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::construct_commit_active(commit::PrioNode& node)
+{
+ auto_ptr<SavePaths> save(create_save_paths());
+ reset_paths();
+ append_cfg_path(node.getCommitPath());
+
+ FsPath ap(get_active_path());
+ FsPath wp(get_work_path());
+ FsPath tap(tmp_active_root);
+ tap /= mutable_cfg_path;
+
+ if (path_exists(tap)) {
+ output_internal("rm[%s]\n", tap.path_cstr());
+ if (b_fs::remove_all(tap.path_cstr()) < 1) {
+ output_user("FAILED\n");
+ return false;
+ }
+ cnode::CfgNode *c = node.getCfgNode();
+ if (c && c->isTag()) {
+ FsPath p(tap);
+ p.pop();
+ if (is_directory_empty(p)) {
+ output_internal("rm[%s]\n", p.path_cstr());
+ if (b_fs::remove_all(p.path_cstr()) < 1) {
+ output_user("FAILED\n");
+ return false;
+ }
+ }
+ }
+ } else {
+ output_internal("no tap[%s]\n", tap.path_cstr());
+ }
+ if (node.succeeded()) {
+ // prio subtree succeeded
+ if (path_exists(wp)) {
+ output_internal("cp[%s]->[%s]\n", wp.path_cstr(), tap.path_cstr());
+ try {
+ recursive_copy_dir(wp, tap, true);
+ } catch (...) {
+ output_user("FAILED\n");
+ return false;
+ }
+ } else {
+ output_internal("no wp[%s]\n", wp.path_cstr());
+ }
+ if (!node.hasSubtreeFailure()) {
+ // whole subtree succeeded => stop recursion
+ return true;
+ }
+ // failure present in subtree
+ } else {
+ // prio subtree failed
+ if (path_exists(ap)) {
+ output_internal("cp[%s]->[%s]\n", ap.path_cstr(), tap.path_cstr());
+ try {
+ recursive_copy_dir(ap, tap, false);
+ } catch (...) {
+ output_user("FAILED\n");
+ return false;
+ }
+ } else {
+ output_internal("no ap[%s]\n", ap.path_cstr());
+ }
+ if (!node.hasSubtreeSuccess()) {
+ // whole subtree failed => stop recursion
+ return true;
+ }
+ // success present in subtree
+ }
+ for (size_t i = 0; i < node.numChildNodes(); i++) {
+ if (!construct_commit_active(*(node.childAt(i)))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::mark_dir_changed(const FsPath& d, const FsPath& root)
+{
+ if (!path_is_directory(d)) {
+ output_internal("mark_dir_changed on non-directory [%s]\n",
+ d.path_cstr());
+ return false;
+ }
+
+ FsPath marker(d);
+ while (marker.size() >= root.size()) {
+ marker.push(C_MARKER_CHANGED);
+ if (path_exists(marker)) {
+ // reached a node already marked => done
+ break;
+ }
+ if (!create_file(marker)) {
+ output_internal("failed to mark changed [%s]\n", marker.path_cstr());
+ return false;
+ }
+ marker.pop();
+ marker.pop();
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::sync_dir(const FsPath& src, const FsPath& dst,
+ const FsPath& root)
+{
+ if (!path_exists(src) || !path_exists(dst)) {
+ output_user("sync_dir with non-existing dir(s)[%s][%s]\n",
+ src.path_cstr(), dst.path_cstr());
+ return false;
+ }
+ MapT<string, bool> smap;
+ MapT<string, bool> dmap;
+ vector<string> sentries;
+ vector<string> dentries;
+ check_dir_entries(src, &sentries, false);
+ check_dir_entries(dst, &dentries, false);
+ for (size_t i = 0; i < sentries.size(); i++) {
+ smap[sentries[i]] = true;
+ }
+ for (size_t i = 0; i < dentries.size(); i++) {
+ dmap[dentries[i]] = true;
+ if (smap.find(dentries[i]) == smap.end()) {
+ // entry in dst but not in src => delete
+ FsPath d(dst);
+ if (!mark_dir_changed(d, root)) {
+ return false;
+ }
+ push_path(d, dentries[i].c_str());
+ if (b_fs::remove_all(d.path_cstr()) < 1) {
+ return false;
+ }
+ } else {
+ // entry in both src and dst
+ FsPath s(src);
+ FsPath d(dst);
+ push_path(s, dentries[i].c_str());
+ push_path(d, dentries[i].c_str());
+ if (path_is_regular(s) && path_is_regular(d)) {
+ // it's file => compare and replace if necessary
+ string ds, dd;
+ if (!read_whole_file(s, ds) || !read_whole_file(d, dd)) {
+ // error
+ output_user("failed to replace file [%s][%s]\n",
+ s.path_cstr(), d.path_cstr());
+ return false;
+ }
+ if (ds != dd) {
+ // need to replace
+ if (!write_file(d, ds)) {
+ output_user("failed to write file [%s]\n", d.path_cstr());
+ return false;
+ }
+ d.pop();
+ if (!mark_dir_changed(d, root)) {
+ return false;
+ }
+ }
+ } else if (path_is_directory(s) && path_is_directory(d)) {
+ // it's dir => recurse
+ if (!sync_dir(s, d, root)) {
+ return false;
+ }
+ } else {
+ // something is wrong
+ output_user("inconsistent config entry [%s][%s]\n",
+ s.path_cstr(), d.path_cstr());
+ return false;
+ }
+ }
+ }
+ for (size_t i = 0; i < sentries.size(); i++) {
+ if (dmap.find(sentries[i]) == dmap.end()) {
+ // entry in src but not in dst => copy
+ FsPath s(src);
+ FsPath d(dst);
+ push_path(s, sentries[i].c_str());
+ push_path(d, sentries[i].c_str());
+ try {
+ if (path_is_regular(s)) {
+ // it's file
+ b_fs::copy_file(s.path_cstr(), d.path_cstr());
+ } else {
+ // dir
+ recursive_copy_dir(s, d, true);
+ }
+ d.pop();
+ if (!mark_dir_changed(d, root)) {
+ return false;
+ }
+ } catch (...) {
+ output_user("copy failed [%s][%s]\n", s.path_cstr(), d.path_cstr());
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool
+UnionfsCstore::commitConfig(commit::PrioNode& node)
+{
+ // make a copy of current "work" dir
+ try {
+ if (path_exists(tmp_work_root)) {
+ output_internal("rm[%s]\n", tmp_work_root.path_cstr());
+ if (b_fs::remove_all(tmp_work_root.path_cstr()) < 1) {
+ output_user("FAILED\n");
+ return false;
+ }
+ }
+ output_internal("cp[%s]->[%s]\n", work_root.path_cstr(),
+ tmp_work_root.path_cstr());
+ recursive_copy_dir(work_root, tmp_work_root, true);
+ } catch (...) {
+ output_user("FAILED\n");
+ return false;
+ }
+
+ if (!construct_commit_active(node)) {
+ return false;
+ }
+
+ if (!do_umount(work_root)) {
+ return false;
+ }
+ if (b_fs::remove_all(change_root.path_cstr()) < 1
+ || b_fs::remove_all(active_root.path_cstr()) < 1) {
+ output_user("failed to remove existing directories\n");
+ return false;
+ }
+ try {
+ b_fs::create_directories(change_root.path_cstr());
+ recursive_copy_dir(tmp_active_root, active_root, true);
+ } catch (...) {
+ return false;
+ }
+ if (!do_mount(change_root, active_root, work_root)) {
+ return false;
+ }
+ if (!sync_dir(tmp_work_root, work_root, work_root)) {
+ return false;
+ }
+#if 0
+ if (b_fs::remove_all(tmp_work_root.path_cstr()) < 1
+ || b_fs::remove_all(tmp_active_root.path_cstr()) < 1) {
+ output_user("failed to remove temp directories\n");
+ return false;
+ }
+#endif
+ // all done
+ return true;
+}
+
////// virtual functions defined in base class
/* check if current tmpl_path is a valid tmpl dir.
@@ -932,58 +1195,6 @@ UnionfsCstore::cfg_node_changed()
return path_exists(marker);
}
-/* XXX currently "committed marking" is done inside commit.
- * TODO move "committed marking" out of commit and into low-level
- * implementation (here).
- */
-/* return whether current "cfg path" has been committed, i.e., whether
- * the set or delete operation on the path has been processed by commit.
- * is_set: whether the operation is set (for sanity check as there can
- * be only one operation on the path).
- */
-bool
-UnionfsCstore::marked_committed(const tr1::shared_ptr<Ctemplate>& def,
- bool is_set)
-{
- FsPath cpath = mutable_cfg_path;
- string com_str = cpath.path_cstr();
- com_str += "/";
- if (def->isLeafValue()) {
- // path includes leaf value. construct the right string.
- string val;
- cpath.pop(val);
- val = _unescape_path_name(val);
- /* XXX current commit implementation escapes value strings for
- * single-value nodes but not for multi-value nodes for some
- * reason. the following match current behavior.
- */
- if (!def->isMulti()) {
- val = _escape_path_name(val);
- }
- com_str = cpath.path_cstr();
- com_str += "/value:";
- com_str += val;
- }
- com_str = (is_set ? "+ " : "- ") + com_str;
- return committed_marker_exists(com_str);
-}
-
-bool
-UnionfsCstore::validate_val_impl(const tr1::shared_ptr<Ctemplate>& def,
- char *value)
-{
- /* XXX filesystem paths/accesses are completely embedded in var ref lib.
- * for now, treat the lib as a unionfs-specific implementation.
- * generalizing it will need a rewrite.
- * set the handle to be used during validate_value() for var ref
- * processing. this is a global var in cli_new.c.
- */
- var_ref_handle = (void *) this;
- bool ret = validate_value(def->getDef(), value);
- var_ref_handle = NULL;
- return ret;
-}
-
void
UnionfsCstore::get_edit_level(Cpath& pcomps) {
FsPath opath = mutable_cfg_path; // use a copy
@@ -999,6 +1210,23 @@ UnionfsCstore::get_edit_level(Cpath& pcomps) {
}
}
+bool
+UnionfsCstore::marked_committed(bool is_delete)
+{
+ string marker;
+ get_committed_marker(is_delete, marker);
+ return find_line_in_file(commit_marker_file, marker);
+}
+
+bool
+UnionfsCstore::mark_committed(bool is_delete)
+{
+ string marker;
+ get_committed_marker(is_delete, marker);
+ // write one marker per line
+ return write_file(commit_marker_file, marker + "\n", true);
+}
+
string
UnionfsCstore::cfg_path_to_str() {
string cpath = mutable_cfg_path.path_cstr();
@@ -1041,36 +1269,48 @@ UnionfsCstore::pop_path(FsPath& path, string& last)
last = _unescape_path_name(last);
}
-void
-UnionfsCstore::get_all_child_dir_names(const FsPath& root,
- vector<string>& nodes)
+bool
+UnionfsCstore::check_dir_entries(const FsPath& root, vector<string> *cnodes,
+ bool filter_nodes, bool empty_check)
{
if (!path_exists(root) || !path_is_directory(root)) {
- // not a valid root. nop.
- return;
+ // not a valid root => treat as empty
+ return false;
}
+ bool found = false;
try {
b_fs::directory_iterator di(root.path_cstr());
for (; di != b_fs::directory_iterator(); ++di) {
- // must be directory
- if (!path_is_directory(di->path().file_string().c_str())) {
- continue;
- }
- // name cannot start with "."
string cname = di->path().filename();
- if (cname.length() < 1 || cname[0] == '.') {
- continue;
+ if (filter_nodes) {
+ // must be directory
+ if (!path_is_directory(di->path().file_string().c_str())) {
+ continue;
+ }
+ // name cannot start with "."
+ if (cname.length() < 1 || cname[0] == '.') {
+ continue;
+ }
}
// found one
- nodes.push_back(_unescape_path_name(cname));
+ if (empty_check) {
+ // only checking and directory is not empty
+ return true;
+ }
+ if (cnodes) {
+ cnodes->push_back(_unescape_path_name(cname));
+ } else {
+ found = true;
+ }
}
} catch (...) {
- return;
+ // skip the rest
}
+ return (cnodes ? (cnodes->size() > 0) : found);
}
bool
-UnionfsCstore::write_file(const FsPath& file, const string& data)
+UnionfsCstore::write_file(const char *file, const string& data, bool append)
{
if (data.size() > C_UNIONFS_MAX_FILE_SIZE) {
output_internal("write_file too large\n");
@@ -1085,7 +1325,10 @@ UnionfsCstore::write_file(const FsPath& file, const string& data)
// write the file
ofstream fout;
fout.exceptions(ofstream::failbit | ofstream::badbit);
- fout.open(file.path_cstr(), ios_base::out | ios_base::trunc);
+ ios_base::openmode mflags = ios_base::out;
+ mflags |= ((!append || !path_exists(file))
+ ? ios_base::trunc : ios_base::app); // truncate or append
+ fout.open(file, mflags);
fout << data;
fout.close();
} catch (...) {
@@ -1127,19 +1370,54 @@ UnionfsCstore::read_whole_file(const FsPath& fpath, string& data)
return true;
}
-/* return whether specified "commited marker" exists in the
- * "committed marker file".
+/* recursively copy source directory to destination.
+ * will throw exception (from b_fs) if fail.
*/
+void
+UnionfsCstore::recursive_copy_dir(const FsPath& src, const FsPath& dst,
+ bool filter_dot_entries)
+{
+ string src_str = src.path_cstr();
+ string dst_str = dst.path_cstr();
+ b_fs::create_directories(dst.path_cstr());
+
+ b_fs::recursive_directory_iterator di(src_str);
+ for (; di != b_fs::recursive_directory_iterator(); ++di) {
+ const char *oname = di->path().file_string().c_str();
+ string nname = oname;
+ nname.replace(0, src_str.length(), dst_str);
+ if (path_is_directory(oname)) {
+ b_fs::create_directory(nname);
+ } else {
+ if (filter_dot_entries) {
+ string of = di->path().filename();
+ if (!of.empty() && of.at(0) == '.') {
+ // filter dot files
+ continue;
+ }
+ }
+ b_fs::copy_file(di->path(), nname);
+ }
+ }
+}
+
+void
+UnionfsCstore::get_committed_marker(bool is_delete, string& marker)
+{
+ marker = (is_delete ? "-" : "");
+ marker += mutable_cfg_path.path_cstr();
+}
+
bool
-UnionfsCstore::committed_marker_exists(const string& marker)
+UnionfsCstore::find_line_in_file(const FsPath& file, const string& line)
{
bool ret = false;
try {
- ifstream fin(C_COMMITTED_MARKER_FILE.c_str());
+ ifstream fin(file.path_cstr());
while (!fin.eof() && !fin.bad() && !fin.fail()) {
- string line;
- getline(fin, line);
- if (line == marker) {
+ string in;
+ getline(fin, in);
+ if (in == line) {
ret = true;
break;
}
@@ -1151,27 +1429,32 @@ UnionfsCstore::committed_marker_exists(const string& marker)
return ret;
}
-/* recursively copy source directory to destination.
- * will throw exception (from b_fs) if fail.
- */
-void
-UnionfsCstore::recursive_copy_dir(const FsPath& src, const FsPath& dst)
+bool
+UnionfsCstore::do_mount(const FsPath& rwdir, const FsPath& rdir,
+ const FsPath& mdir)
{
- string src_str = src.path_cstr();
- string dst_str = dst.path_cstr();
- b_fs::create_directory(dst.path_cstr());
+ string mopts = "dirs=";
+ mopts += rwdir.path_cstr();
+ mopts += "=rw:";
+ mopts += rdir.path_cstr();
+ mopts += "=ro";
+ if (mount("unionfs", mdir.path_cstr(), "unionfs", 0, mopts.c_str()) != 0) {
+ output_internal("union mount failed [%s][%s]\n",
+ strerror(errno), mdir.path_cstr());
+ return false;
+ }
+ return true;
+}
- b_fs::recursive_directory_iterator di(src_str);
- for (; di != b_fs::recursive_directory_iterator(); ++di) {
- const char *oname = di->path().file_string().c_str();
- string nname = oname;
- nname.replace(0, src_str.length(), dst_str);
- if (path_is_directory(oname)) {
- b_fs::create_directory(nname);
- } else {
- b_fs::copy_file(oname, nname);
- }
+bool
+UnionfsCstore::do_umount(const FsPath& mdir)
+{
+ if (umount(mdir.path_cstr()) != 0) {
+ output_internal("union umount failed [%s][%s]\n",
+ strerror(errno), mdir.path_cstr());
+ return false;
}
+ return true;
}
bool
diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp
index 7c73225..8113d00 100644
--- a/src/cstore/unionfs/cstore-unionfs.hpp
+++ b/src/cstore/unionfs/cstore-unionfs.hpp
@@ -30,6 +30,11 @@
#include <cstore/cstore.hpp>
#include <cstore/unionfs/fspath.hpp>
+// forward decl
+namespace commit {
+class PrioNode;
+}
+
namespace cstore { // begin namespace cstore
namespace unionfs { // begin namespace unionfs
@@ -50,6 +55,8 @@ public:
bool setupSession();
bool teardownSession();
bool inSession();
+ bool clearCommittedMarkers();
+ bool commitConfig(commit::PrioNode& pnode);
private:
// constants
@@ -85,7 +92,7 @@ private:
FsPath work_root; // working root (union)
FsPath active_root; // active root (readonly part of union)
FsPath change_root; // change root (r/w part of union)
- FsPath tmp_root; // temp root
+ FsPath tmp_root; // temp root
FsPath tmpl_root; // template root
// path buffers
@@ -94,6 +101,22 @@ private:
FsPath orig_mutable_cfg_path; // original mutable cfg path
FsPath orig_tmpl_path; // original template path
+ // for commit processing
+ FsPath tmp_active_root;
+ FsPath tmp_work_root;
+ FsPath commit_marker_file;
+ void init_commit_data() {
+ tmp_active_root = tmp_root;
+ tmp_work_root = tmp_root;
+ commit_marker_file = tmp_root;
+ tmp_active_root.push("active");
+ tmp_work_root.push("work");
+ commit_marker_file.push(C_COMMITTED_MARKER_FILE);
+ }
+ bool construct_commit_active(commit::PrioNode& node);
+ bool mark_dir_changed(const FsPath& d, const FsPath& root);
+ bool sync_dir(const FsPath& src, const FsPath& dst, const FsPath& root);
+
////// virtual functions defined in base class
// begin path modifiers
void push_tmpl_path(const char *new_comp) {
@@ -196,12 +219,6 @@ private:
bool get_comment(string& comment, bool active_cfg);
bool marked_display_default(bool active_cfg);
- // observers during commit operation
- bool marked_committed(const tr1::shared_ptr<Ctemplate>& def, bool is_set);
-
- // these operate on both current tmpl and work paths
- bool validate_val_impl(const tr1::shared_ptr<Ctemplate>& def, char *value);
-
// observers for "edit/tmpl levels" (for "edit"-related operations).
// note that these should be moved to base class in the future.
string get_edit_level_path() {
@@ -215,6 +232,10 @@ private:
return cfg_path_at_root();
};
+ // functions for commit operation
+ bool marked_committed(bool is_delete);
+ bool mark_committed(bool is_delete);
+
// for testing/debugging
string cfg_path_to_str();
string tmpl_path_to_str();
@@ -226,14 +247,33 @@ private:
void push_path(FsPath& old_path, const char *new_comp);
void pop_path(FsPath& path);
void pop_path(FsPath& path, string& last);
- void get_all_child_dir_names(const FsPath& root, vector<string>& nodes);
- bool write_file(const FsPath& file, const string& data);
+ bool check_dir_entries(const FsPath& root, vector<string> *cnodes,
+ bool filter_nodes = true, bool empty_check = false);
+ bool is_directory_empty(const FsPath& d) {
+ return (!check_dir_entries(d, NULL, false, true));
+ }
+ void get_all_child_dir_names(const FsPath& root, vector<string>& nodes) {
+ check_dir_entries(root, &nodes);
+ }
+ bool write_file(const char *file, const string& data,
+ bool append = false);
+ bool write_file(const FsPath& file, const string& data,
+ bool append = false) {
+ return write_file(file.path_cstr(), data, append);
+ }
+ bool create_file(const char *file) {
+ return write_file(file, "");
+ };
bool create_file(const FsPath& file) {
return write_file(file, "");
};
bool read_whole_file(const FsPath& file, string& data);
- bool committed_marker_exists(const string& marker);
- void recursive_copy_dir(const FsPath& src, const FsPath& dst);
+ void recursive_copy_dir(const FsPath& src, const FsPath& dst,
+ bool filter_dot_entries = false);
+ void get_committed_marker(bool is_delete, string& marker);
+ bool find_line_in_file(const FsPath& file, const string& line);
+ bool do_mount(const FsPath& rwdir, const FsPath& rdir, const FsPath& mdir);
+ bool do_umount(const FsPath& mdir);
// boost fs operations wrappers
bool b_fs_get_file_status(const char *path, b_fs::file_status& fs) {
diff --git a/src/cstore/unionfs/fspath.hpp b/src/cstore/unionfs/fspath.hpp
index 35985ed..fbaafd6 100644
--- a/src/cstore/unionfs/fspath.hpp
+++ b/src/cstore/unionfs/fspath.hpp
@@ -65,6 +65,7 @@ public:
};
size_t length() const { return _data.length(); };
+ size_t size() const { return _data.size(); };
bool has_parent_path() const { return (_data.size() > 0); };
const char *path_cstr() const { return _data.get_cstr(); };
size_t hash() const { return _data.hash(); };