diff options
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | debian/vyatta-cfg.postinst.in | 2 | ||||
-rw-r--r-- | src/cli_bin.cpp | 35 | ||||
-rw-r--r-- | src/cli_cstore.h | 7 | ||||
-rw-r--r-- | src/cli_new.c | 24 | ||||
-rw-r--r-- | src/cli_objects.h | 5 | ||||
-rw-r--r-- | src/cli_shell_api.cpp | 1 | ||||
-rw-r--r-- | src/cli_val.h | 4 | ||||
-rw-r--r-- | src/cnode/cnode-algorithm.cpp | 64 | ||||
-rw-r--r-- | src/cnode/cnode-algorithm.hpp | 53 | ||||
-rw-r--r-- | src/cnode/cnode-util.hpp | 84 | ||||
-rw-r--r-- | src/cnode/cnode.cpp | 40 | ||||
-rw-r--r-- | src/cnode/cnode.hpp | 56 | ||||
-rw-r--r-- | src/commit/commit-algorithm.cpp | 1191 | ||||
-rw-r--r-- | src/commit/commit-algorithm.hpp | 196 | ||||
-rw-r--r-- | src/cparse/cparse.hpp | 6 | ||||
-rw-r--r-- | src/cparse/cparse.ypp | 2 | ||||
-rw-r--r-- | src/cstore/cstore-varref.cpp | 40 | ||||
-rw-r--r-- | src/cstore/cstore.cpp | 83 | ||||
-rw-r--r-- | src/cstore/cstore.hpp | 26 | ||||
-rw-r--r-- | src/cstore/ctemplate.hpp | 19 | ||||
-rw-r--r-- | src/cstore/unionfs/cstore-unionfs.cpp | 495 | ||||
-rw-r--r-- | src/cstore/unionfs/cstore-unionfs.hpp | 62 | ||||
-rw-r--r-- | src/cstore/unionfs/fspath.hpp | 1 |
24 files changed, 2219 insertions, 283 deletions
diff --git a/Makefile.am b/Makefile.am index 7791616..78c1a66 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,6 +45,7 @@ src_libvyatta_cfg_la_SOURCES += src/cnode/cnode.cpp src_libvyatta_cfg_la_SOURCES += src/cnode/cnode-algorithm.cpp src_libvyatta_cfg_la_SOURCES += src/cparse/cparse.cpp src_libvyatta_cfg_la_SOURCES += src/cparse/cparse_lex.c +src_libvyatta_cfg_la_SOURCES += src/commit/commit-algorithm.cpp CLEANFILES = src/cli_parse.c src/cli_parse.h src/cli_def.c src/cli_val.c CLEANFILES += src/cparse/cparse.cpp src/cparse/cparse.h CLEANFILES += src/cparse/cparse_lex.c @@ -71,7 +72,6 @@ vpincdir = $(vincludedir)/cparse vpinc_HEADERS = src/cparse/cparse.hpp sbin_PROGRAMS = src/priority -sbin_PROGRAMS += src/my_commit sbin_PROGRAMS += src/exe_action sbin_PROGRAMS += src/dump sbin_PROGRAMS += src/check_tmpl @@ -79,7 +79,6 @@ sbin_PROGRAMS += src/my_cli_bin sbin_PROGRAMS += src/my_cli_shell_api src_priority_SOURCES = src/priority.c -src_my_commit_SOURCES = src/commit2.cpp src_exe_action_SOURCES = src/exe_action.c src_dump_SOURCES = src/dump_session.c src_check_tmpl_SOURCES = src/check_tmpl.c @@ -129,7 +128,8 @@ install-exec-hook: $(LN_S) my_cli_bin my_copy; \ $(LN_S) my_cli_bin my_comment; \ $(LN_S) my_cli_bin my_discard; \ - $(LN_S) my_cli_bin my_move + $(LN_S) my_cli_bin my_move; \ + $(LN_S) my_cli_bin my_commit mkdir -p $(DESTDIR)/bin cd $(DESTDIR)/bin ; \ $(LN_S) $(sbindir)/my_cli_shell_api cli-shell-api diff --git a/debian/vyatta-cfg.postinst.in b/debian/vyatta-cfg.postinst.in index 237dc99..5585e2a 100644 --- a/debian/vyatta-cfg.postinst.in +++ b/debian/vyatta-cfg.postinst.in @@ -28,7 +28,7 @@ if [ "$sysconfdir" != "/etc" ]; then fi # capability stuff -for bin in my_cli_bin my_cli_shell_api my_commit; do +for bin in my_cli_bin my_cli_shell_api; do touch -ac $sbindir/$bin setcap cap_sys_admin=pe $sbindir/$bin done 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(); }; |