From 639c835bc2730a4fbffd915f5b2028a68375ee7a Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Wed, 28 Jul 2010 14:30:32 -0700 Subject: add new cstore library --- src/cstore/cstore-c.cpp | 159 +++ src/cstore/cstore-c.h | 55 + src/cstore/cstore-varref.cpp | 288 ++++ src/cstore/cstore-varref.hpp | 48 + src/cstore/cstore.cpp | 2496 +++++++++++++++++++++++++++++++++ src/cstore/cstore.hpp | 409 ++++++ src/cstore/unionfs/cstore-unionfs.cpp | 1078 ++++++++++++++ src/cstore/unionfs/cstore-unionfs.hpp | 217 +++ 8 files changed, 4750 insertions(+) create mode 100644 src/cstore/cstore-c.cpp create mode 100644 src/cstore/cstore-c.h create mode 100644 src/cstore/cstore-varref.cpp create mode 100644 src/cstore/cstore-varref.hpp create mode 100644 src/cstore/cstore.cpp create mode 100644 src/cstore/cstore.hpp create mode 100644 src/cstore/unionfs/cstore-unionfs.cpp create mode 100644 src/cstore/unionfs/cstore-unionfs.hpp (limited to 'src/cstore') diff --git a/src/cstore/cstore-c.cpp b/src/cstore/cstore-c.cpp new file mode 100644 index 0000000..3215707 --- /dev/null +++ b/src/cstore/cstore-c.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "cstore-c.h" +#include "cstore/unionfs/cstore-unionfs.hpp" + +void * +cstore_init(void) +{ + Cstore *handle = new UnionfsCstore(); + return (void *) handle; +} + +void +cstore_free(void *handle) +{ + UnionfsCstore *h = (UnionfsCstore *) handle; + delete h; +} + +int +cstore_validate_tmpl_path(void *handle, const char *path_comps[], + int num_comps, int validate_tags) +{ + if (handle) { + vector vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->validateTmplPath(vs, validate_tags) ? 1 : 0); + } + return 0; +} + +int +cstore_validate_tmpl_path_d(void *handle, const char *path_comps[], + int num_comps, int validate_tags, vtw_def *def) +{ + if (handle) { + vector vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->validateTmplPath(vs, validate_tags, *def) ? 1 : 0); + } + return 0; +} + +int +cstore_cfg_path_exists(void *handle, const char *path_comps[], int num_comps) +{ + if (handle) { + vector vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->cfgPathExists(vs) ? 1 : 0); + } + return 0; +} + +int +cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval, + int from_active) +{ + if (handle) { + Cstore *cs = (Cstore *) handle; + return (cs->getVarRef(ref_str, *cval, from_active) ? 1 : 0); + } + return 0; +} + +int +cstore_set_var_ref(void *handle, const char *ref_str, const char *value, + int to_active) +{ + if (handle) { + Cstore *cs = (Cstore *) handle; + return (cs->setVarRef(ref_str, value, to_active) ? 1 : 0); + } + return 0; +} + +int +cstore_cfg_path_deactivated(void *handle, const char *path_comps[], + int num_comps, int in_active) +{ + if (handle) { + vector vs; + for (int i = 0; i < num_comps; i++) { + vs.push_back(path_comps[i]); + } + Cstore *cs = (Cstore *) handle; + return (cs->cfgPathDeactivated(vs, in_active) ? 1 : 0); + } + return 0; +} + +char ** +cstore_path_string_to_path_comps(const char *path_str, int *num_comps) +{ + char *pstr = strdup(path_str); + size_t len = strlen(pstr); + vector vec; + char *start = NULL; + for (unsigned int i = 0; i < len; i++) { + if (pstr[i] == '/') { + if (start) { + pstr[i] = 0; + vec.push_back(start); + pstr[i] = '/'; + start = NULL; + } + continue; + } else if (!start) { + start = &(pstr[i]); + } + } + if (start) { + vec.push_back(start); + } + char **ret = (char **) malloc(sizeof(char *) * vec.size()); + for (unsigned int i = 0; i < vec.size(); i++) { + ret[i] = strdup(vec[i].c_str()); + } + *num_comps = vec.size(); + free(pstr); + return ret; +} + +void +cstore_free_path_comps(char **path_comps, int num_comps) +{ + for (int i = 0; i < num_comps; i++) { + free(path_comps[i]); + } + free(path_comps); +} + diff --git a/src/cstore/cstore-c.h b/src/cstore/cstore-c.h new file mode 100644 index 0000000..e664f95 --- /dev/null +++ b/src/cstore/cstore-c.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 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 . + */ + +#ifndef _CSTORE_C_H_ +#define _CSTORE_C_H_ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +void *cstore_init(void); +void cstore_free(void *handle); +int cstore_validate_tmpl_path(void *handle, const char *path_comps[], + int num_comps, int validate_tags); +int cstore_validate_tmpl_path_d(void *handle, const char *path_comps[], + int num_comps, int validate_tags, + vtw_def *def); +int cstore_cfg_path_exists(void *handle, const char *path_comps[], + int num_comps); +int cstore_cfg_path_deactivated(void *handle, const char *path_comps[], + int num_comps, int in_active); + +/* the following are internal APIs for the library. they can only be used + * during cstore operations since they operate on "current" paths constructed + * by the operations. + */ +int cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval, + int from_active); +int cstore_set_var_ref(void *handle, const char *ref_str, const char *value, + int to_active); + +/* util functions */ +char **cstore_path_string_to_path_comps(const char *path_str, int *num_comps); +void cstore_free_path_comps(char **path_comps, int num_comps); + +#ifdef __cplusplus +} +#endif +#endif /* _CSTORE_C_H_ */ + diff --git a/src/cstore/cstore-varref.cpp b/src/cstore/cstore-varref.cpp new file mode 100644 index 0000000..6d71307 --- /dev/null +++ b/src/cstore/cstore-varref.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "cstore-varref.hpp" + +extern "C" { +#include "cli_val.h" +#include "cli_objects.h" +} + +using namespace std; + +////// constructors/destructors +Cstore::VarRef::VarRef(Cstore *cstore, const string& ref_str, bool active) + : _cstore(cstore), _active(active) +{ + /* NOTE: this class will change the paths in the cstore. caller must do + * save/restore for the cstore if necessary. + */ + if (!_cstore) { + // no cstore + return; + } + + _absolute = (ref_str[0] == '/'); + while (!_absolute && !_cstore->cfg_path_at_root()) { + _orig_path_comps.insert(_orig_path_comps.begin(), + _cstore->pop_cfg_path()); + } + _cstore->reset_paths(); + /* at this point, cstore paths are at root. _orig_path_comps contains + * the path originally in cstore (or empty if _absolute). + */ + + size_t si = (_absolute ? 1 : 0); + size_t sn = 0; + vector rcomps; + while (si < ref_str.length() + && (sn = ref_str.find('/', si)) != ref_str.npos) { + rcomps.push_back(ref_str.substr(si, sn - si)); + si = sn + 1; + } + if (si < ref_str.length()) { + rcomps.push_back(ref_str.substr(si)); + } + // NOTE: if path ends in '/', the trailing slash is ignored. + + // get the "at" string. this is set inside cli_new.c. + _at_string = get_at_string(); + + // process ref + vector pcomps = _orig_path_comps; + process_ref(rcomps, pcomps, ERROR_TYPE); +} + +/* process the reference(s). + * this is a recursive function and always keeps the cstore paths unchanged + * between invocations. + * + * note: def_type is added into _paths along with the paths. when it's + * ERROR_TYPE, it means the path needs to be checked for existence. + * otherwise, the path is a "value" (or "values") read from the + * actual config (working or active). + */ +void +Cstore::VarRef::process_ref(const vector& ref_comps, + const vector& cur_path_comps, + vtw_type_e def_type) +{ + if (ref_comps.size() == 0) { + // done + _paths.push_back(pair, + vtw_type_e>(cur_path_comps, def_type)); + return; + } + + vector rcomps = ref_comps; + vector pcomps = cur_path_comps; + string cr_comp= rcomps.front(); + rcomps.erase(rcomps.begin()); + + vtw_def def; + bool got_tmpl = _cstore->get_parsed_tmpl(pcomps, false, def); + bool handle_leaf = false; + if (cr_comp == "@") { + if (!got_tmpl) { + // invalid path + return; + } + if (def.def_type == ERROR_TYPE) { + // no value for typeless node + return; + } + if (pcomps.size() == _orig_path_comps.size()) { + if (pcomps.size() == 0 + || equal(pcomps.begin(), pcomps.end(), _orig_path_comps.begin())) { + /* we are at the original path. this is a self-reference, e.g., + * $VAR(@), so use the "at string". + */ + pcomps.push_back(_at_string); + process_ref(rcomps, pcomps, def.def_type); + return; + } + } + if (pcomps.size() < _orig_path_comps.size()) { + // within the original path. @ translates to the path comp. + pcomps.push_back(_orig_path_comps[pcomps.size()]); + process_ref(rcomps, pcomps, def.def_type); + return; + } + if (def.is_value || def.tag) { + // invalid ref + return; + } + // handle leaf node + handle_leaf = true; + } else if (cr_comp == ".") { + process_ref(rcomps, pcomps, ERROR_TYPE); + } else if (cr_comp == "..") { + if (!got_tmpl || pcomps.size() == 0) { + // invalid path + return; + } + pcomps.pop_back(); + if (!_cstore->get_parsed_tmpl(pcomps, false, def)) { + // invalid tmpl path + return; + } + if (def.is_value && def.tag) { + // at "tag value", need to pop one more. + if (pcomps.size() == 0) { + // invalid path + return; + } + pcomps.pop_back(); + } + process_ref(rcomps, pcomps, ERROR_TYPE); + } else if (cr_comp == "@@") { + if (!got_tmpl) { + // invalid path + return; + } + if (def.def_type == ERROR_TYPE) { + // no value for typeless node + return; + } + if (def.is_value) { + // invalid ref + return; + } + if (def.tag) { + // tag node + vector cnodes; + _cstore->cfgPathGetChildNodes(pcomps, cnodes, _active); + for (unsigned int i = 0; i < cnodes.size(); i++) { + pcomps.push_back(cnodes[i]); + process_ref(rcomps, pcomps, def.def_type); + pcomps.pop_back(); + } + } else { + // handle leaf node + handle_leaf = true; + } + } else { + // just text. go down 1 level. + if (got_tmpl && def.tag && !def.is_value) { + // at "tag node". need to go down 1 more level. + if (pcomps.size() >= _orig_path_comps.size()) { + // already under the original node. invalid ref. + return; + } + // within the original path. take the original tag value. + pcomps.push_back(_orig_path_comps[pcomps.size()]); + } + pcomps.push_back(cr_comp); + process_ref(rcomps, pcomps, ERROR_TYPE); + } + + if (handle_leaf) { + if (def.multi) { + // multi-value node + vector vals; + if (!_cstore->cfgPathGetValues(pcomps, vals, _active)) { + return; + } + string val; + for (unsigned int i = 0; i < vals.size(); i++) { + if (val.length() > 0) { + val += " "; + } + val += vals[i]; + } + pcomps.push_back(val); + // treat "joined" multi-values as TEXT_TYPE + _paths.push_back(pair, vtw_type_e>(pcomps, TEXT_TYPE)); + // at leaf. stop recursion. + } else { + // single-value node + string val; + if (!_cstore->cfgPathGetValue(pcomps, val, _active)) { + return; + } + pcomps.push_back(val); + _paths.push_back(pair, vtw_type_e>(pcomps, def.def_type)); + // at leaf. stop recursion. + } + } +} + +bool +Cstore::VarRef::getValue(string& value, vtw_type_e& def_type) +{ + vector result; + map added; + def_type = ERROR_TYPE; + for (unsigned int i = 0; i < _paths.size(); i++) { + if (_paths[i].first.size() == 0) { + // empty path + continue; + } + if (added.find(_paths[i].first.back()) != added.end()) { + // already added + continue; + } + if (_paths[i].second == ERROR_TYPE + && !_cstore->cfgPathExists(_paths[i].first, _active)) { + // path doesn't exist + continue; + } + if (_paths[i].second != ERROR_TYPE) { + // set def_type. all types should be the same if multiple entries exist. + def_type = _paths[i].second; + } + added[_paths[i].first.back()] = true; + result.push_back(_paths[i].first.back()); + } + if (result.size() == 0) { + // got nothing + return false; + } + if (result.size() > 1 || def_type == ERROR_TYPE) { + /* if no type is available or we are returning "joined" multiple values, + * treat it as text type. + */ + def_type = TEXT_TYPE; + } + value = ""; + for (unsigned int i = 0; i < result.size(); i++) { + if (i > 0) { + value += " "; + } + value += result[i]; + } + return true; +} + +bool +Cstore::VarRef::getSetPath(vector& path_comps) +{ + /* XXX this function is currently unused and untested. see setVarRef() + * in Cstore for more information. + */ + if (_paths.size() != 1) { + // for set_var_ref operation, there can be only one path. + return false; + } + path_comps = _paths[0].first; + return true; +} + diff --git a/src/cstore/cstore-varref.hpp b/src/cstore/cstore-varref.hpp new file mode 100644 index 0000000..1fc1d52 --- /dev/null +++ b/src/cstore/cstore-varref.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 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 . + */ + +#ifndef _CSTORE_VARREF_H_ +#define _CSTORE_VARREF_H_ +#include +#include +#include + +#include "cstore.hpp" + +using namespace std; + +class Cstore::VarRef { +public: + VarRef(Cstore *cstore, const string& ref_str, bool active); + ~VarRef() {}; + + bool getValue(string& value, vtw_type_e& def_type); + bool getSetPath(vector& path_comps); + +private: + Cstore *_cstore; + bool _active; + bool _absolute; + string _at_string; + vector _orig_path_comps; + vector, vtw_type_e> > _paths; + + void process_ref(const vector& ref_comps, + const vector& cur_path_comps, vtw_type_e def_type); +}; + +#endif /* _CSTORE_VARREF_H_ */ + diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp new file mode 100644 index 0000000..31c896a --- /dev/null +++ b/src/cstore/cstore.cpp @@ -0,0 +1,2496 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "cli_val.h" +} + +#include "cstore.hpp" +#include "cstore-varref.hpp" + + +////// constants +//// node status +const string Cstore::C_NODE_STATUS_DELETED = "deleted"; +const string Cstore::C_NODE_STATUS_ADDED = "added"; +const string Cstore::C_NODE_STATUS_CHANGED = "changed"; +const string Cstore::C_NODE_STATUS_STATIC = "static"; + +//// env vars for shell +// current levels +const string Cstore::C_ENV_EDIT_LEVEL = "VYATTA_EDIT_LEVEL"; +const string Cstore::C_ENV_TMPL_LEVEL = "VYATTA_TEMPLATE_LEVEL"; + +// shell-specific vars +const string Cstore::C_ENV_SHELL_PROMPT = "PS1"; +const string Cstore::C_ENV_SHELL_CWORDS = "COMP_WORDS"; +const string Cstore::C_ENV_SHELL_CWORD_COUNT = "COMP_CWORD"; + +// shell api vars +const string Cstore::C_ENV_SHAPI_COMP_VALS = "_cli_shell_api_comp_values"; +const string Cstore::C_ENV_SHAPI_LCOMP_VAL = "_cli_shell_api_last_comp_val"; +const string Cstore::C_ENV_SHAPI_COMP_HELP = "_cli_shell_api_comp_help"; +const string Cstore::C_ENV_SHAPI_HELP_ITEMS = "_cli_shell_api_hitems"; +const string Cstore::C_ENV_SHAPI_HELP_STRS = "_cli_shell_api_hstrs"; + +//// dirs +const string Cstore::C_ENUM_SCRIPT_DIR = "/opt/vyatta/share/enumeration"; + +////// constructors/destructors +/* this constructor just returns the generic environment string, + * currently the two levels. implementation-specific environment + * (e.g., unionfs stuff) is handled by derived class. + * + * note: currently using original semantics for the levels, i.e., they + * represent the actual physical paths, which involve fs-specific + * escaping. this should be changed to a "logical" representation + * so that their manipulation can be moved from derived class to + * this base class. + */ +Cstore::Cstore(string& env) +{ + string decl = "declare -x "; + env = (decl + C_ENV_EDIT_LEVEL + "=/; "); + env += (decl + C_ENV_TMPL_LEVEL + "=/;"); +} + + +////// public interface +/* check if specified "logical path" corresponds to a valid template. + * validate_vals: whether to validate "values" along specified path. + * return true if valid. otherwise return false. + */ +bool +Cstore::validateTmplPath(const vector& path_comps, bool validate_vals) +{ + vtw_def def; + // if we can get parsed tmpl, path is valid + return get_parsed_tmpl(path_comps, validate_vals, def); +} + +/* same as above but return parsed template. + * def: (output) parsed template. + * note: if last path component is "value" (i.e., def.is_value), parsed + * template is actually at "full path - 1". see get_parsed_tmpl() for details. + */ +bool +Cstore::validateTmplPath(const vector& path_comps, bool validate_vals, + vtw_def& def) +{ + // if we can get parsed tmpl, path is valid + return get_parsed_tmpl(path_comps, validate_vals, def); +} + +/* get parsed template of specified path as a string-string map + * tmap: (output) parsed template. + * return true if successful. otherwise return false. + */ +bool +Cstore::getParsedTmpl(const vector& path_comps, + map& tmap, bool allow_val) +{ + vtw_def def; + /* currently this function is used outside actual CLI operations, mainly + * from the perl API. since value validation is from the original CLI + * implementation, it doesn't seem to behave correctly in such cases, + * probably because "at string" is not set? + * + * anyway, not validating values in the following call. + */ + if (!get_parsed_tmpl(path_comps, false, def)) { + return false; + } + if (!allow_val && def.is_value) { + /* note: !allow_val means specified path must terminate at an actual + * "node", not a "value". so this fails since path ends in value. + * this emulates the original perl API behavior. + */ + return false; + } + if (def.is_value) { + tmap["is_value"] = "1"; + } + // make the map + if (def.def_type != ERROR_TYPE) { + tmap["type"] = type_to_name(def.def_type); + } + if (def.def_type2 != ERROR_TYPE) { + tmap["type2"] = type_to_name(def.def_type2); + } + if (def.def_node_help) { + tmap["help"] = def.def_node_help; + } + if (def.multi) { + tmap["multi"] = "1"; + if (def.def_multi > 0) { + ostringstream s; + s << def.def_multi; + tmap["limit"] = s.str(); + } + } else if (def.tag) { + tmap["tag"] = "1"; + if (def.def_tag > 0) { + ostringstream s; + s << def.def_tag; + tmap["limit"] = s.str(); + } + } else if (def.def_default) { + tmap["default"] = def.def_default; + } + if (def.def_enumeration) { + tmap["enum"] = def.def_enumeration; + } + if (def.def_allowed) { + tmap["allowed"] = def.def_allowed; + } + if (def.def_val_help) { + tmap["val_help"] = def.def_val_help; + } + return true; +} + +/* get names of all template child nodes of specified path. + * cnodes: (output) template child node names. + * note: if specified path is at a "tag node", "node.tag" will be returned. + */ +void +Cstore::tmplGetChildNodes(const vector& path_comps, + vector& cnodes) +{ + SAVE_PATHS; + append_tmpl_path(path_comps); + get_all_tmpl_child_node_names(cnodes); + RESTORE_PATHS; +} + +/* delete specified "logical path" from "working config". + * def: parsed template corresponding to logical path path_comps. + * return true if successful. otherwise return false. + * note: assume specified path has been validated + * (i.e., validateDeletePath()). + */ +bool +Cstore::deleteCfgPath(const vector& path_comps, const vtw_def& def) +{ + if (!cfg_path_exists(path_comps, false, true)) { + output_user("Nothing to delete (the specified %s does not exist)\n", + (!def.is_value || def.tag) ? "node" : "value"); + // treat as success + return true; + } + + /* path already validated and in working config. + * cases: + * 1. has default value + * => replace current value with default + * 2. no default value + * => remove config path + */ + if (def.def_default) { + // case 1. construct path for value file. + SAVE_PATHS; + append_cfg_path(path_comps); + if (def.is_value) { + // last comp is "value". need to go up 1 level. + pop_cfg_path(); + } + + /* assume default value is valid (parser should have validated). + * also call unmark_deactivated() in case the node being deleted was + * also deactivated. note that unmark_deactivated() succeeds if it's + * not marked deactivated. + */ + bool ret = (write_value(def.def_default) && mark_display_default() + && unmark_deactivated()); + if (!ret) { + output_user("Failed to set default value during delete\n"); + } + RESTORE_PATHS; + return ret; + } + + /* case 2. + * sub-cases: + * (1) last path comp is "value", i.e., tag (value of tag node), + * value of single-value node, or value of multi-value node. + * (a) value of single-value node + * => remove node + * (b) value of multi-value node + * => remove value. remove node if last value. + * (c) value of tag node (i.e., tag) + * => remove tag. remove node if last tag. + * (2) last path comp is "node", i.e., typeless node, tag node, + * single-value node, or multi-value node. + * => remove node + */ + bool ret = false; + SAVE_PATHS; + append_cfg_path(path_comps); + if (!def.is_value) { + // sub-case (2) + ret = remove_node(); + } else { + // last comp is value + if (def.tag) { + // sub-case (1c) + ret = remove_tag(); + } else if (def.multi) { + // sub-case (1b) + pop_cfg_path(); + ret = remove_value_from_multi(path_comps[path_comps.size() - 1]); + } else { + // sub-case (1a). delete node at 1 level up. + pop_cfg_path(); + ret = remove_node(); + } + } + RESTORE_PATHS; + if (!ret) { + output_user("Failed to delete specified config path\n"); + } + return ret; +} + +/* check if specified "logical path" is valid for "set" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateSetPath(const vector& path_comps) +{ + // if we can get parsed tmpl, path is valid + vtw_def def; + string terr; + if (!get_parsed_tmpl(path_comps, true, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + + bool ret = true; + SAVE_PATHS; + if (!def.is_value) { + if (def.def_type != ERROR_TYPE) { + /* disallow setting value node without value + * note: different from old behavior, which only disallow setting a + * single-value node without value. now all value nodes + * (single-value, multi-value, and tag) must be set with value. + */ + output_user("The specified configuration node requires a value\n"); + ret = false; + } else { + /* typeless node + * note: XXX the following is present in the original logic, perhaps + * to trigger check_syn() on the typeless node? is this really + * necessary? + * also, validate_val() uses current cfg path and tmpl path, so + * construct them before calling it. + */ + append_cfg_path(path_comps); + append_tmpl_path(path_comps); + if (!validate_val(&def, "")) { + ret = false; + } + } + } + RESTORE_PATHS; + return ret; +} + +/* check if specified "logical path" is valid for "delete" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateDeletePath(const vector& path_comps, vtw_def& def) +{ + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + return true; +} + +/* check if specified "logical path" is valid for "activate" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateActivatePath(const vector& path_comps) +{ + vtw_def def; + if (!validate_act_deact(path_comps, "activate", def)) { + return false; + } + if (!cfgPathMarkedDeactivated(path_comps)) { + output_user("Activate can only be performed on a node on which the " + "deactivate\ncommand has been performed.\n"); + return false; + } + bool ret = true; + if (def.is_value && def.tag && def.def_tag > 0) { + // we are activating a tag, and there is a limit on number of tags. + vector cnodes; + SAVE_PATHS; + append_cfg_path(path_comps); + string t = pop_cfg_path(); + // get child nodes, excluding deactivated ones. + get_all_child_node_names(cnodes, false, false); + if (def.def_tag <= cnodes.size()) { + // limit exceeded + output_user("Cannot activate \"%s\": number of values exceeds limit " + "(%d allowed)\n", t.c_str(), def.def_tag); + ret = false; + } + RESTORE_PATHS; + } + return ret; +} + +/* check if specified "logical path" is valid for "deactivate" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateDeactivatePath(const vector& path_comps) +{ + vtw_def def; + return validate_act_deact(path_comps, "deactivate", def); +} + +/* check if specified "logical path" is valid for "edit" operation. + * return false if invalid. + * if valid, set "env" arg to the environment string needed for the "edit" + * operation and return true. + */ +bool +Cstore::getEditEnv(const vector& path_comps, string& env) +{ + vtw_def def; + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + if (!cfg_path_exists(path_comps, false, true)) { + output_user("The specified config path does not exist\n"); + return false; + } + /* "edit" is only allowed when path ends at a + * (1) "tag value" + * OR + * (2) "typeless node" + */ + if (!(def.is_value && def.tag) + && !(!def.is_value && def.def_type == ERROR_TYPE)) { + // neither "tag value" nor "typeless node" + output_user("The \"edit\" command cannot be issued " + "at the specified level\n"); + return false; + } + SAVE_PATHS; + append_cfg_path(path_comps); + append_tmpl_path(path_comps); + get_edit_env(env); + RESTORE_PATHS; + /* doing the save/restore above to be consistent with the rest of the API. + * however, after the caller evals the returned environment string, the + * levels in "this" will become out-of-sync with the environment. so + * "this" should no longer be used and a new object should be created. + * + * this is only an issue if the calling process doesn't terminate. since + * the function should only be used by the shell/completion, it's not a + * problem (each invocation of my_cli_shell_api uses a new object anyway). + */ + + return true; +} + +/* set "env" arg to the environment string needed for the "up" operation. + * return true if successful. + */ +bool +Cstore::getEditUpEnv(string& env) +{ + /* "up" is based on current levels in environment. levels should already + * be set up in constructor (with "use_edit_level" true). + */ + if (edit_level_at_root()) { + output_user("Already at the top level\n"); + return false; + } + + /* get_parsed_tmpl() does not allow empty path, so use one component + * from current paths. + */ + vtw_def def; + string terr; + vector path_comps; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + // this should not happen since it's using existing levels + output_user("%s\n", terr.c_str()); + return false; + } + SAVE_PATHS; + if (def.is_value && def.tag) { + // edit level is at "tag value". go up 1 extra level. + pop_cfg_path(); + pop_tmpl_path(); + } + pop_cfg_path(); + pop_tmpl_path(); + get_edit_env(env); + RESTORE_PATHS; + // also see getEditEnv for comment on save/restore above + + return true; +} + +/* set "env" arg to the environment string needed to reset edit levels. + * return true if successful. + */ +bool +Cstore::getEditResetEnv(string& env) +{ + SAVE_PATHS; + while (!edit_level_at_root()) { + pop_cfg_path(); + pop_tmpl_path(); + } + get_edit_env(env); + RESTORE_PATHS; + // also see getEditEnv for comment on save/restore above + + return true; +} + +/* set "env" arg to the environment string needed for "completion". + * return true if successful. + * + * note: comps must have at least 2 components, the "command" and the + * first path element (which can be empty string). + */ +bool +Cstore::getCompletionEnv(const vector& comps, string& env) +{ + vector pcomps = comps; + string cmd = pcomps[0]; + string last_comp = pcomps.back(); + pcomps.erase(pcomps.begin()); + pcomps.pop_back(); + bool exists_only = (cmd == "delete" || cmd == "show" || cmd == "edit" + || cmd == "comment" || cmd == "activate" + || cmd == "deactivate"); + + /* at this point, pcomps contains the command line arguments minus the + * "command" and the last one. + */ + bool ret = false; + SAVE_PATHS; + do { + vtw_def def; + if (pcomps.size() > 0) { + if (!get_parsed_tmpl(pcomps, false, def)) { + // invalid path + break; + } + if (exists_only && !cfg_path_exists(pcomps, false, true)) { + // invalid path for the command (must exist) + break; + } + append_cfg_path(pcomps); + append_tmpl_path(pcomps); + } else { + // we are at root. simulate a typeless node. + def.def_type = ERROR_TYPE; + def.tag = def.multi = def.is_value = 0; + } + + /* at this point, cfg and tmpl paths are constructed up to the comp + * before last_comp, and def is parsed. + */ + if (def.is_value && !def.tag) { + // invalid path (this means the comp before last_comp is a leaf value) + break; + } + + vector comp_vals; + string comp_string; + string comp_help; + vector > help_pairs; + bool last_comp_val = true; + if (def.def_type == ERROR_TYPE || def.is_value) { + /* path so far is at a typeless node OR a tag value (tag already + * checked above): + * completions: from tmpl children. + * help: + * values: same as completions. + * text: "help" from child templates. + * + * note: for such completions, we filter non-existent nodes if + * necessary. + */ + vector ufvec; + if (exists_only) { + // only return existing config nodes + get_all_child_node_names(ufvec, false, true); + } else { + // return all template children + get_all_tmpl_child_node_names(ufvec); + } + for (unsigned int i = 0; i < ufvec.size(); i++) { + if (last_comp == "" + || ufvec[i].compare(0, last_comp.length(), last_comp) == 0) { + comp_vals.push_back(ufvec[i]); + } + } + if (comp_vals.size() == 0) { + // no matches + break; + } + sort(comp_vals.begin(), comp_vals.end()); + for (unsigned int i = 0; i < comp_vals.size(); i++) { + pair hpair(comp_vals[i], ""); + push_tmpl_path(hpair.first); + vtw_def cdef; + if (tmpl_parse(cdef)) { + hpair.second = cdef.def_node_help; + } else { + hpair.second = ""; + } + help_pairs.push_back(hpair); + pop_tmpl_path(); + } + // last comp is not value + last_comp_val = false; + } else { + /* path so far is at a "value node". + * note: follow the original implementation and don't filter + * non-existent values for such completions + */ + // first, handle completions. + if (def.tag) { + // it's a "tag node". get completions from tag values. + get_all_child_node_names(comp_vals, false, true); + } else { + // it's a "leaf value node". get completions from values. + read_value_vec(comp_vals, false); + } + /* more possible completions from this node's template: + * "allowed" + * "enumeration" + * "$VAR(@) in ..." + */ + if (def.def_enumeration || def.def_allowed) { + /* do "enumeration" or "allowed". + * note: emulate original implementation and set up COMP_WORDS and + * COMP_CWORD environment variables. these are needed by some + * "allowed" scripts. + */ + ostringstream cword_count; + cword_count << (comps.size() - 1); + string cmd_str = ("export " + C_ENV_SHELL_CWORD_COUNT + "=" + + cword_count.str() + "; "); + cmd_str += ("export " + C_ENV_SHELL_CWORDS + "=("); + for (unsigned int i = 0; i < comps.size(); i++) { + cmd_str += (" '" + comps[i] + "'"); + } + cmd_str += "); "; + if (def.def_enumeration) { + cmd_str += (C_ENUM_SCRIPT_DIR + "/" + def.def_enumeration); + } else { + cmd_str += def.def_allowed; + } + + char *buf = (char *) malloc(MAX_CMD_OUTPUT_SIZE); + int ret = get_shell_command_output(cmd_str.c_str(), buf, + MAX_CMD_OUTPUT_SIZE); + if (ret > 0) { + // '<' and '>' need to be escaped + char *ptr = buf; + while (*ptr) { + if (*ptr == '<' || *ptr == '>') { + comp_string += "\\"; + } + comp_string += *ptr; + ptr++; + } + } + /* note that for "enumeration" and "allowed", comp_string is the + * complete output of the command and it is to be evaled by the + * shell into an array of values. + */ + free(buf); + } else if (def.actions[syntax_act].vtw_list_head) { + // look for "self ref in values" from syntax + const valstruct *vals = get_syntax_self_in_valstruct( + def.actions[syntax_act].vtw_list_head); + if (vals) { + if (vals->cnt == 0 && vals->val) { + comp_vals.push_back(vals->val); + } else if (vals->cnt > 0) { + for (int i = 0; i < vals->cnt; i++) { + if (vals->vals[i]) { + comp_vals.push_back(vals->vals[i]); + } + } + } + } + } + + // now handle help. + if (def.def_comp_help) { + // "comp_help" exists. + comp_help = def.def_comp_help; + shell_escape_squotes(comp_help); + } + if (def.def_val_help) { + // has val_help. first separate individual lines. + unsigned int start = 0, i = 0; + vector vhelps; + for (i = 0; def.def_val_help[i]; i++) { + if (def.def_val_help[i] == '\n') { + vhelps.push_back(string(&(def.def_val_help[start]), i - start)); + start = i + 1; + } + } + if (start < i) { + vhelps.push_back(string(&(def.def_val_help[start]), i - start)); + } + + // process each line + for (i = 0; i < vhelps.size(); i++) { + size_t sc; + if ((sc = vhelps[i].find(';')) == vhelps[i].npos) { + // no ';' + if (i == 0 && def.def_type != ERROR_TYPE) { + // first val_help. pair with "type". + help_pairs.push_back(pair( + type_to_name(def.def_type), vhelps[i])); + } + if (i == 1 && def.def_type2 != ERROR_TYPE) { + // second val_help. pair with "type2". + help_pairs.push_back(pair( + type_to_name(def.def_type2), vhelps[i])); + } + } else { + // ';' at index sc + help_pairs.push_back(pair( + vhelps[i].substr(0, sc), + vhelps[i].substr(sc + 1))); + } + } + } else if (def.def_type && def.def_node_help) { + // simple case. just use "type" and "help" + help_pairs.push_back(pair(type_to_name(def.def_type), + def.def_node_help)); + } + } + + // this var is the array of possible completions + env = (C_ENV_SHAPI_COMP_VALS + "=("); + for (unsigned int i = 0; i < comp_vals.size(); i++) { + shell_escape_squotes(comp_vals[i]); + env += ("'" + comp_vals[i] + "' "); + } + /* as mentioned above, comp_string is the complete command output. + * let the shell eval it into the array since we don't want to + * re-implement the shell interpretation here. + * + * note that as a result, we will not be doing the filtering here. + * instead, the completion script will do the filtering on + * the resulting comp_values array. should be straightforward since + * there's no "existence filtering", only "prefix filtering". + */ + env += (comp_string + "); "); + + /* this var indicates whether the last comp is "value" + * follow original implementation: if last comp is value, completion + * script needs to do the following. + * use comp_help if exists + * prefix filter comp_values + * replace any <*> in comp_values with "" + * convert help items to data representation + */ + env += (C_ENV_SHAPI_LCOMP_VAL + "="); + env += (last_comp_val ? "true; " : "false; "); + + // this var is the "comp_help" string + env += (C_ENV_SHAPI_COMP_HELP + "='" + comp_help + "'; "); + + // this var is the array of "help items", i.e., type names, etc. + string hitems = (C_ENV_SHAPI_HELP_ITEMS + "=("); + // this var is the array of "help strings" corresponding to the items + string hstrs = (C_ENV_SHAPI_HELP_STRS + "=("); + for (unsigned int i = 0; i < help_pairs.size(); i++) { + string hi = help_pairs[i].first; + string hs = help_pairs[i].second; + shell_escape_squotes(hi); + shell_escape_squotes(hs); + // get rid of leading/trailing "space" chars in help string + while (hi.size() > 0 && isspace(hi[0])) { + hi.erase(0, 1); + } + while (hs.size() > 0 && isspace(hs[0])) { + hs.erase(0, 1); + } + while (hi.size() > 0 && isspace(hi[hi.size() - 1])) { + hi.erase(hi.size() - 1); + } + while (hs.size() > 0 && isspace(hs[hs.size() - 1])) { + hs.erase(hs.size() - 1); + } + hitems += ("'" + hi + "' "); + hstrs += ("'" + hs + "' "); + } + env += (hitems + "); " + hstrs + "); "); + ret = true; + } while(0); + RESTORE_PATHS; + return ret; +} + +/* set specified "logical path" in "working config". + * return true if successful. otherwise return false. + * note: assume specified path is valid (i.e., validateSetPath()). + */ +bool +Cstore::setCfgPath(const vector& path_comps) +{ + vector ppath; + vtw_def def; + bool ret = true; + bool path_exists = true; + // do the set from the top down + SAVE_PATHS; + for (unsigned int i = 0; i < path_comps.size(); i++) { + // partial path + ppath.push_back(path_comps[i]); + + // get template at this level + if (!get_parsed_tmpl(ppath, false, def)) { + output_user("paths[%s,%s]\n", cfg_path_to_str().c_str(), + tmpl_path_to_str().c_str()); + for (unsigned int i = 0; i < ppath.size(); i++) { + output_user(" [%s]\n", ppath[i].c_str()); + } + exit_internal("failed to get tmpl during set. not validate first?\n"); + } + + // nop if this level already in working (including deactivated) + if (cfg_path_exists(ppath, false, true)) { + continue; + } + path_exists = false; + + // this level not in working. set it. + append_cfg_path(ppath); + append_tmpl_path(ppath); + + if (!def.is_value) { + // this level is a "node" + if (!add_node() || !create_default_children()) { + ret = false; + break; + } + } else if (def.tag) { + // this level is a "tag value". + // add the tag, taking the max tag limit into consideration. + if (!add_tag(def) || !create_default_children()) { + ret = false; + break; + } + } else { + // this level is a "value" of a single-/multi-value node. + // go up 1 level to get the node. + pop_cfg_path(); + if (def.multi) { + // value of multi-value node. + // add the value, taking the max multi limit into consideration. + if (!add_value_to_multi(def, ppath.back())) { + ret = false; + break; + } + } else { + // value of single-value node + if (!write_value(ppath.back())) { + ret = false; + break; + } + } + } + RESTORE_PATHS; + } + RESTORE_PATHS; // if "break" was hit + + if (ret && def.is_value && def.def_default) { + /* a node with default has been explicitly set. needs to be marked + * as non-default for display purposes. + * + * note: when the new value is the same as the old value, the behavior + * is different from before, which was a nop. the new behavior is + * that the value will remain unchanged, but the "default status" + * will be changed, i.e., it will be marked as non-default. + */ + append_cfg_path(path_comps); + pop_cfg_path(); + // only do it if it's previously marked default + if (marked_display_default()) { + ret = unmark_display_default(); + + /* XXX work around current commit's unionfs implementation problem. + * current commit's unionfs implementation looks at the "changes only" + * directory (i.e., the r/w portion of the union mount), which is wrong. + * + * all config information should be obtained from two directories: + * "active" and "working", e.g., instead of looking at whiteout files + * in "changes only" to find deleted nodes, nodes that are in "active" + * but not in "working" have been deleted. + * + * in this particular case, commit looks at "changes only" to read the + * node.val file. however, since the value didn't change (only the + * "default status" changed), node.val doesn't appear in "changes only". + * here we re-write the file to force it into "changes only" so that + * commit can work correctly. + */ + vector vvec; + read_value_vec(vvec, false); + write_value_vec(vvec); + + // pretend it didn't exist since we changed the status + path_exists = false; + } + RESTORE_PATHS; + } + if (path_exists) { + // whole path already exists + output_user("The specified configuration node already exists\n"); + // treat as success + } + return ret; +} + +/* check if specified "arguments" is valid for "rename" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateRenameArgs(const vector& args) +{ + return validate_rename_copy(args, "rename"); +} + +/* check if specified "arguments" is valid for "copy" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateCopyArgs(const vector& args) +{ + return validate_rename_copy(args, "copy"); +} + +/* check if specified "arguments" is valid for "move" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateMoveArgs(const vector& args) +{ + vector epath; + vector nargs; + if (!conv_move_args_for_rename(args, epath, nargs)) { + output_user("Invalid move command\n"); + return false; + } + SAVE_PATHS; + append_cfg_path(epath); + append_tmpl_path(epath); + bool ret = validate_rename_copy(nargs, "move"); + RESTORE_PATHS; + return ret; +} + +/* check if specified "arguments" is valid for "comment" operation + * return true if valid. otherwise return false. + */ +bool +Cstore::validateCommentArgs(const vector& args, vtw_def& def) +{ + /* separate path from comment. + * follow the original implementation: the last arg is the comment, and + * everything else is part of the path. + */ + vector path_comps(args); + string comment = args.back(); + path_comps.pop_back(); + + // check the path + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + // here we want to include deactivated nodes + if (!cfg_path_exists(path_comps, false, true)) { + output_user("The specified config node does not exist\n"); + return false; + } + if (def.is_value && !def.tag) { + /* XXX differ from the original implementation, which allows commenting + * on a "value" BUT silently "promote" the comment to the parent + * "node". this will probably create confusion for the user. + * + * just disallow such cases here. + */ + output_user("Cannot comment on config values\n"); + return false; + } + if (def.tag && !def.is_value) { + /* XXX follow original implementation and disallow comment on a + * "tag node". this is because "show" does not display such + * comments (see bug 5794). + */ + output_user("Cannot add comment at this level\n"); + return false; + } + if (comment.find_first_of('*') != string::npos) { + // don't allow '*'. this is due to config files using C-style /**/ + // comments. this probably belongs to lower-level, but we are enforcing + // it here. + output_user("Cannot use the '*' character in a comment\n"); + return false; + } + return true; +} + +/* perform rename in "working config" according to specified args. + * return true if successful. otherwise return false. + * note: assume args are already validated (i.e., validateRenameArgs()). + */ +bool +Cstore::renameCfgPath(const vector& args) +{ + string otagnode = args[0]; + string otagval = args[1]; + string ntagval = args[4]; + push_cfg_path(otagnode); + bool ret = rename_child_node(otagval, ntagval); + pop_cfg_path(); + return ret; +} + +/* perform copy in "working config" according to specified args. + * return true if successful. otherwise return false. + * note: assume args are already validated (i.e., validateCopyArgs()). + */ +bool +Cstore::copyCfgPath(const vector& args) +{ + string otagnode = args[0]; + string otagval = args[1]; + string ntagval = args[4]; + push_cfg_path(otagnode); + bool ret = copy_child_node(otagval, ntagval); + pop_cfg_path(); + return ret; +} + +/* perform "comment" in working config according to specified args. + * return true if valid. otherwise return false. + */ +bool +Cstore::commentCfgPath(const vector& args, const vtw_def& def) +{ + // separate path from comment + vector path_comps(args); + string comment = args.back(); + path_comps.pop_back(); + + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret; + if (comment == "") { + // follow original impl: empty comment => remove it + ret = remove_comment(); + if (!ret) { + output_user("Failed to remove comment for specified config node\n"); + } + } else { + ret = set_comment(comment); + if (!ret) { + output_user("Failed to add comment for specified config node\n"); + } + } + RESTORE_PATHS; + return ret; +} + +/* discard all changes in working config. + * return true if successful. otherwise return false. + */ +bool +Cstore::discardChanges() +{ + // just call underlying implementation + unsigned long long num_removed = 0; + if (discard_changes(num_removed)) { + if (num_removed > 0) { + output_user("Changes have been discarded\n"); + } else { + output_user("No changes have been discarded\n"); + } + return true; + } + return false; +} + +/* perform move in "working config" according to specified args. + * return true if successful. otherwise return false. + * note: assume args are already validated (i.e., validateMoveArgs()). + */ +bool +Cstore::moveCfgPath(const vector& args) +{ + vector epath; + vector nargs; + if (!conv_move_args_for_rename(args, epath, nargs)) { + output_user("Invalid move command\n"); + return false; + } + SAVE_PATHS; + append_cfg_path(epath); + append_tmpl_path(epath); + bool ret = renameCfgPath(nargs); + RESTORE_PATHS; + return ret; +} + +/* check if specified "logical path" exists in working config (i.e., the union) + * or active config (i.e., the original). + * return true if it exists. otherwise return false. + */ +bool +Cstore::cfgPathExists(const vector& path_comps, bool active_cfg) +{ + return cfg_path_exists(path_comps, active_cfg, false); +} + +// same as above but "deactivate-aware" +bool +Cstore::cfgPathExistsDA(const vector& path_comps, bool active_cfg, + bool include_deactivated) +{ + return cfg_path_exists(path_comps, active_cfg, include_deactivated); +} + +/* check if specified "logical path" has been deleted in working config. + */ +bool +Cstore::cfgPathDeleted(const vector& path_comps) +{ + // whether it's in active but not in working + return (cfg_path_exists(path_comps, true, false) + && !cfg_path_exists(path_comps, false, false)); +} + +/* check if specified "logical path" has been added in working config. + */ +bool +Cstore::cfgPathAdded(const vector& path_comps) +{ + // whether it's not in active but in working + return (!cfg_path_exists(path_comps, true, false) + && cfg_path_exists(path_comps, false, false)); +} + +/* check if specified "logical path" has been "changed" in working config. + * XXX the definition of "changed" is different from the original + * perl API implementation isChanged(), which was inconsistent between + * "deleted" and "deactivated". + * + * original logic (with $disable arg not defined) returns true in + * either of the 2 cases below: + * (1) node is BEING deactivated or activated + * (2) node appears in changes_only dir + * which means it returns false for nodes being deleted but true + * for nodes being deactivated. + * + * new logic returns true if any of the following is true + * (remember this functions is NOT "deactivate-aware") + * (1) cfgPathDeleted() + * (2) cfgPathAdded() + * (3) marked_changed() + */ +bool +Cstore::cfgPathChanged(const vector& path_comps) +{ + if (cfgPathDeleted(path_comps) || cfgPathAdded(path_comps)) { + return true; + } + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = marked_changed(); + RESTORE_PATHS; + return ret; +} + +/* get names of "deleted" child nodes of specified path during commit + * operation. names are returned in cnodes. + */ +void +Cstore::cfgPathGetDeletedChildNodes(const vector& path_comps, + vector& cnodes) +{ + cfgPathGetDeletedChildNodesDA(path_comps, cnodes, false); +} + +// same as above but "deactivate-aware" +void +Cstore::cfgPathGetDeletedChildNodesDA(const vector& path_comps, + vector& cnodes, + bool include_deactivated) +{ + vector acnodes; + cfgPathGetChildNodesDA(path_comps, acnodes, true, include_deactivated); + vector wcnodes; + cfgPathGetChildNodesDA(path_comps, wcnodes, false, include_deactivated); + map cmap; + for (unsigned int i = 0; i < wcnodes.size(); i++) { + cmap[wcnodes[i]] = true; + } + for (unsigned int i = 0; i < acnodes.size(); i++) { + if (cmap.find(acnodes[i]) == cmap.end()) { + // in active but not in working + cnodes.push_back(acnodes[i]); + } + } +} + +/* this is the equivalent of the listNodeStatus() from the original + * perl API. it provides the "status" ("deleted", "added", "changed", + * or "static") of each child node of specified path. + * cmap: (output) contains the status of child nodes. + * + * note: this function is NOT "deactivate-aware". + */ +void +Cstore::cfgPathGetChildNodesStatus(const vector& path_comps, + map& cmap) +{ + // get a union of active and working + map umap; + vector acnodes; + vector wcnodes; + cfgPathGetChildNodes(path_comps, acnodes, true); + cfgPathGetChildNodes(path_comps, wcnodes, false); + for (unsigned int i = 0; i < acnodes.size(); i++) { + umap[acnodes[i]] = true; + } + for (unsigned int i = 0; i < wcnodes.size(); i++) { + umap[wcnodes[i]] = true; + } + + // get the status of each one + vector ppath = path_comps; + map::iterator it = umap.begin(); + for (; it != umap.end(); ++it) { + string c = (*it).first; + ppath.push_back(c); + // note: "changed" includes "deleted" and "added", so check those first. + if (cfgPathDeleted(ppath)) { + cmap[c] = C_NODE_STATUS_DELETED; + } else if (cfgPathAdded(ppath)) { + cmap[c] = C_NODE_STATUS_ADDED; + } else if (cfgPathChanged(ppath)) { + cmap[c] = C_NODE_STATUS_CHANGED; + } else { + cmap[c] = C_NODE_STATUS_STATIC; + } + ppath.pop_back(); + } +} + +/* DA version of the above function. + * cmap: (output) contains the status of child nodes. + * + * note: this follows the original perl API listNodeStatus() implementation. + */ +void +Cstore::cfgPathGetChildNodesStatusDA(const vector& path_comps, + map& cmap) +{ + // process deleted nodes first + vector del_nodes; + cfgPathGetDeletedChildNodesDA(path_comps, del_nodes); + for (unsigned int i = 0; i < del_nodes.size(); i++) { + cmap[del_nodes[i]] = C_NODE_STATUS_DELETED; + } + + // get all nodes in working config + vector work_nodes; + cfgPathGetChildNodesDA(path_comps, work_nodes, false); + vector ppath = path_comps; + for (unsigned int i = 0; i < work_nodes.size(); i++) { + ppath.push_back(work_nodes[i]); + /* note: in the DA version here, we do NOT check the deactivate state + * when considering the state of the child nodes (which include + * deactivated ones). the reason is that this DA function is used + * for config output-related operations and should return whether + * each node is actually added/deleted from the config independent + * of its deactivate state. + * + * for "added" state, can't use cfgPathAdded() since it's not DA. + * + * deleted ones already handled above. + */ + if (!cfg_path_exists(ppath, true, true) + && cfg_path_exists(ppath, false, true)) { + cmap[work_nodes[i]] = C_NODE_STATUS_ADDED; + } else if (cfgPathChanged(ppath)) { + cmap[work_nodes[i]] = C_NODE_STATUS_CHANGED; + } else { + cmap[work_nodes[i]] = C_NODE_STATUS_STATIC; + } + ppath.pop_back(); + } +} + +/* check whether specified path is "deactivated" in working config or + * active config. + * a node is "deactivated" if the node itself or any of its ancestors is + * "marked deactivated". + */ +bool +Cstore::cfgPathDeactivated(const vector& path_comps, bool active_cfg) +{ + vector ppath; + for (unsigned int i = 0; i < path_comps.size(); i++) { + ppath.push_back(path_comps[i]); + if (cfgPathMarkedDeactivated(ppath, active_cfg)) { + // an ancestor or itself is marked deactivated + return true; + } + } + return false; +} + +/* check whether specified path is "marked deactivated" in working config or + * active config. + * a node is "marked deactivated" if a deactivate operation has been + * performed on the node. + */ +bool +Cstore::cfgPathMarkedDeactivated(const vector& path_comps, + bool active_cfg) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = marked_deactivated(active_cfg); + RESTORE_PATHS; + return ret; +} + +/* get names of child nodes of specified path in working config or active + * config. names are returned in cnodes. + */ +void +Cstore::cfgPathGetChildNodes(const vector& path_comps, + vector& cnodes, bool active_cfg) +{ + cfgPathGetChildNodesDA(path_comps, cnodes, active_cfg, false); +} + +// same as above but "deactivate-aware" +void +Cstore::cfgPathGetChildNodesDA(const vector& path_comps, + vector& cnodes, bool active_cfg, + bool include_deactivated) +{ + if (!include_deactivated && cfgPathDeactivated(path_comps, active_cfg)) { + /* this node is deactivated (an ancestor or this node itself is + * marked deactivated) and we don't want to include deactivated. nop. + */ + return; + } + SAVE_PATHS; + append_cfg_path(path_comps); + get_all_child_node_names(cnodes, active_cfg, include_deactivated); + RESTORE_PATHS; +} + +/* get value of specified single-value node. + * value: (output) node value. + * active_cfg: whether to get value from active config. + * return false if fails (invalid node, doesn't exist, read fails, etc.). + * otherwise return true. + */ +bool +Cstore::cfgPathGetValue(const vector& path_comps, string& value, + bool active_cfg) +{ + return cfgPathGetValueDA(path_comps, value, active_cfg, false); +} + +// same as above but "deactivate-aware" +bool +Cstore::cfgPathGetValueDA(const vector& path_comps, string& value, + bool active_cfg, bool include_deactivated) +{ + vtw_def def; + if (!get_parsed_tmpl(path_comps, false, def)) { + // invalid node + return false; + } + /* note: the behavior here is different from original perl API, which + * does not check if specified node is indeed single-value. so if + * the function is erroneously used on a multi-value node, the + * original API will return a single string that includes all values. + * this new function will return failure in such cases. + */ + if (def.is_value || def.multi || def.tag || def.def_type == ERROR_TYPE) { + // specified path is not a single-value node + return false; + } + if (!cfg_path_exists(path_comps, active_cfg, include_deactivated)) { + // specified node doesn't exist + return false; + } + vector vvec; + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = false; + if (read_value_vec(vvec, active_cfg)) { + if (vvec.size() >= 1) { + // if for some reason we got multiple values, just take the first one. + value = vvec[0]; + ret = true; + } + } + RESTORE_PATHS; + return ret; +} + +/* get values of specified multi-value node. + * values: (output) node values. + * active_cfg: whether to get values from active config. + * return false if fails (invalid node, doesn't exist, etc.). + * otherwise return true. + */ +bool +Cstore::cfgPathGetValues(const vector& path_comps, + vector& values, bool active_cfg) +{ + return cfgPathGetValuesDA(path_comps, values, active_cfg, false); +} + +// same as above but "deactivate-aware" +bool +Cstore::cfgPathGetValuesDA(const vector& path_comps, + vector& values, bool active_cfg, + bool include_deactivated) +{ + vtw_def def; + if (!get_parsed_tmpl(path_comps, false, def)) { + // invalid node + return false; + } + /* note: the behavior here is different from original perl API, which + * does not check if specified node is indeed multi-value. so if + * the function is erroneously used on a single-value node, the + * original API will return the node's value. this new function + * will return failure in such cases. + */ + if (def.is_value || !def.multi || def.tag || def.def_type == ERROR_TYPE) { + // specified path is not a multi-value node + return false; + } + if (!cfg_path_exists(path_comps, active_cfg, include_deactivated)) { + // specified node doesn't exist + return false; + } + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = read_value_vec(values, active_cfg); + RESTORE_PATHS; + return ret; +} + +/* get comment of specified node. + * comment: (output) node comment. + * active_cfg: whether to get comment from active config. + * return false if fails (invalid node, doesn't exist, etc.). + * otherwise return true. + */ +bool +Cstore::cfgPathGetComment(const vector& path_comps, string& comment, + bool active_cfg) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = get_comment(comment, active_cfg); + RESTORE_PATHS; + return ret; +} + +/* the following functions are observers of the "effective" config. + * they can be used + * (1) outside a config session (e.g., op mode, daemons, callbacks, etc.). + * OR + * (2) during a config session + * + * HOWEVER, NOTE that the definition of "effective" is different under these + * two scenarios. + * (1) when used outside a config session, "effective" == "active". + * in other words, in such cases the effective config is the same + * as the running config. + * + * (2) when used during a config session, a config path (leading to either + * a "node" or a "value") is "effective" if ANY of the following + * is true. + * (a) active && working + * path is in both active and working configs, i.e., unchanged. + * (b) !active && working && committed + * path is not in active, has been set in working, AND has + * already been committed, i.e., "commit" has successfully + * processed the addition/update of the path. + * (c) active && !working && !committed + * path is in active, has been deleted from working, AND + * has not been committed yet, i.e., "commit" (per priority) has + * not processed the deletion of the path yet, or it has been + * processed but failed. + * + * note: during commit, deactivate has the same effect as delete. so + * in such cases, as far as these functions are concerned, + * deactivated nodes don't exist. + * + * originally, these functions are exclusively for use during config + * sessions. however, for some usage scenarios, it is useful to have a set + * of API functions that can be used both during and outside config + * sessions. therefore, definition (1) is added above for convenience. + * + * for example, a developer can use these functions in a script that can + * be used both during a commit action and outside config mode, as long as + * the developer is clearly aware of the difference between the above two + * definitions. + * + * note that when used outside a config session (i.e., definition (1)), + * these functions are equivalent to the observers for the "active" config. + * + * to avoid any confusiton, when possible (e.g., in a script that is + * exclusively used in op mode), developers should probably use those + * "active" observers explicitly when outside a config session instead + * of these "effective" observers. + * + * it is also important to note that when used outside a config session, + * due to race conditions, it is possible that the "observed" active config + * becomes out-of-sync with the config that is actually "in effect". + * specifically, this happens when two things occur simultaneously: + * (a) an observer function is called from outside a config session. + * AND + * (b) someone invokes "commit" inside a config session (any session). + * + * this is because "commit" only updates the active config at the end after + * all commit actions have been executed, so before the update happens, + * some config nodes have already become "effective" but are not yet in the + * "active config" and therefore are not observed by these functions. + * + * note that this is only a problem when the caller is outside config mode. + * in such cases, the caller (which could be an op-mode command, a daemon, + * a callback script, etc.) already must be able to handle config changes + * that can happen at any time. if "what's configured" is more important, + * using the "active config" should be fine as long as it is relatively + * up-to-date. if the actual "system state" is more important, then the + * caller should probably just check the system state in the first place + * (instead of using these config observers). + * + * one possible solution is for these "effective" observers to obtain the + * global commit lock before returning their observations. this has not + * been implemented yet since the impact of this issue is not clear at + * the moment. + */ + +// return whether specified path is "effective". +bool +Cstore::cfgPathEffective(const vector& path_comps) +{ + vtw_def def; + if (!validateTmplPath(path_comps, false, def)) { + // invalid path + return false; + } + + bool in_active = cfg_path_exists(path_comps, true, false); + if (!inSession()) { + // not in a config session. use active config only. + return in_active; + } + + bool in_work = cfg_path_exists(path_comps, false, false); + if (in_active && in_work) { + // case (1) + return true; + } + bool ret = false; + SAVE_PATHS; + append_cfg_path(path_comps); + if (!in_active && in_work) { + // check if case (2) + ret = marked_committed(def, true); + } else if (in_active && !in_work) { + // check if case (3) + ret = !marked_committed(def, false); + } + RESTORE_PATHS; + + return ret; +} + +/* get names of "effective" child nodes of specified path during commit + * operation. see above function for definition of "effective". + * names are returned in cnodes. + */ +void +Cstore::cfgPathGetEffectiveChildNodes(const vector& path_comps, + vector& cnodes) +{ + if (!inSession()) { + // not in a config session. use active config only. + cfgPathGetChildNodes(path_comps, cnodes, true); + return; + } + + // get a union of active and working + map cmap; + vector acnodes; + vector wcnodes; + cfgPathGetChildNodes(path_comps, acnodes, true); + cfgPathGetChildNodes(path_comps, wcnodes, false); + for (unsigned int i = 0; i < acnodes.size(); i++) { + cmap[acnodes[i]] = true; + } + for (unsigned int i = 0; i < wcnodes.size(); i++) { + cmap[wcnodes[i]] = true; + } + + // get only the effective ones from the union + vector ppath = path_comps; + map::iterator it = cmap.begin(); + for (; it != cmap.end(); ++it) { + string c = (*it).first; + ppath.push_back(c); + if (cfgPathEffective(ppath)) { + cnodes.push_back(c); + } + ppath.pop_back(); + } +} + +/* get the "effective" value of specified path during commit operation. + * value: (output) node value + * return true if successful. otherwise return false. + */ +bool +Cstore::cfgPathGetEffectiveValue(const vector& path_comps, + string& value) +{ + if (!inSession()) { + // not in a config session. use active config only. + return cfgPathGetValue(path_comps, value, true); + } + + vector ppath = path_comps; + string oval, nval; + bool oret = cfgPathGetValue(path_comps, oval, true); + bool nret = cfgPathGetValue(path_comps, nval, false); + bool ret = false; + // all 4 combinations of oret and nret are covered below + if (nret) { + // got new value + ppath.push_back(nval); + if (cfgPathEffective(ppath)) { + // nval already effective + value = nval; + ret = true; + } else if (!oret) { + // no oval. failure. + } else { + // oval still effective + value = oval; + ret = true; + } + } else if (oret) { + // got oval only + ppath.push_back(oval); + if (cfgPathEffective(ppath)) { + // oval still effective + value = oval; + ret = true; + } + } + return ret; +} + +/* get the "effective" values of specified path during commit operation. + * values: (output) node values + * return true if successful. otherwise return false. + */ +bool +Cstore::cfgPathGetEffectiveValues(const vector& path_comps, + vector& values) +{ + if (!inSession()) { + // not in a config session. use active config only. + cfgPathGetValues(path_comps, values, true); + return (values.size() > 0); + } + + // get a union of active and working + map vmap; + vector ovals; + vector nvals; + cfgPathGetValues(path_comps, ovals, true); + cfgPathGetValues(path_comps, nvals, false); + for (unsigned int i = 0; i < ovals.size(); i++) { + vmap[ovals[i]] = true; + } + for (unsigned int i = 0; i < nvals.size(); i++) { + vmap[nvals[i]] = true; + } + + // get only the effective ones from the union + vector ppath = path_comps; + map::iterator it = vmap.begin(); + for (; it != vmap.end(); ++it) { + string c = (*it).first; + ppath.push_back(c); + if (cfgPathEffective(ppath)) { + values.push_back(c); + } + ppath.pop_back(); + } + return (values.size() > 0); +} + +/* get the value string that corresponds to specified variable ref string. + * ref_str: var ref string (e.g., "./cost/@"). + * cval: (output) contains the resulting string. + * from_active: if true, value string should come from "active config". + * otherwise from "working config". + * return true if successful. otherwise return false. + */ +bool +Cstore::getVarRef(const string& ref_str, clind_val& cval, bool from_active) +{ + bool ret = true; + SAVE_PATHS; + VarRef vref(this, ref_str, from_active); + string val; + vtw_type_e type; + if (!vref.getValue(val, type)) { + ret = false; + } else { + cval.val_type = type; + // follow original implementation. caller is supposed to free this. + cval.value = strdup(val.c_str()); + } + RESTORE_PATHS; + return ret; +} + +/* set the node corresponding to specified variable ref string to specified + * value. + * ref_str: var ref string (e.g., "../encrypted-password/@"). + * value: value to be set. + * to_active: if true, set in "active config". + * otherwise in "working config". + * return true if successful. otherwise return false. + */ +bool +Cstore::setVarRef(const string& ref_str, const string& value, bool to_active) +{ + /* XXX functions in cli_new only performs "set var ref" operations (e.g., + * '$VAR(@) = ""', which sets current node's value to empty string) + * during "commit", i.e., if a "set var ref" is specified in + * "syntax:", it will not be performed during "set" (but will be + * during commit). + * + * since commit has not been converted to use the new library, it + * does not use this function. instead, it uses the "cli_val_engine" + * implementation (where filesystem paths are deeply embedded, which + * makes it difficult to abstract low-level filesystem operations + * from high-level functions). as a result, this function is unused + * and untested at the moment. must revisit when converting commit. + */ + SAVE_PATHS; + VarRef vref(this, ref_str, to_active); + vector pcomps; + bool ret = false; + if (vref.getSetPath(pcomps)) { + reset_paths(); + vtw_def def; + if (get_parsed_tmpl(pcomps, false, def)) { + if (!def.is_value && !def.tag && !def.multi + && def.def_type != ERROR_TYPE) { + // currently only support single-value node + append_cfg_path(pcomps); + if (write_value(value, to_active)) { + ret = true; + } + } + } + } + RESTORE_PATHS; + return ret; +} + +/* perform deactivate operation on a node, i.e., make the node + * "marked deactivated". + * note: assume all validations have been peformed (see activate.cpp). + * also, when marking a node as deactivated, all of its descendants + * that had been marked deactivated are unmarked. + */ +bool +Cstore::markCfgPathDeactivated(const vector& path_comps) +{ + if (cfgPathDeactivated(path_comps)) { + output_user("The specified configuration node is already deactivated\n"); + // treat as success + return true; + } + + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = (mark_deactivated() && unmark_deactivated_descendants()); + RESTORE_PATHS; + return ret; +} + +/* perform activate operation on a node, i.e., make the node no longer + * "marked deactivated". + * note: assume all validations have been peformed (see activate.cpp). + */ +bool +Cstore::unmarkCfgPathDeactivated(const vector& path_comps) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = unmark_deactivated(); + RESTORE_PATHS; + return ret; +} + +/* mark specified path as changed in working config. + * the marking is used during commit to check if a node has been changed. + * this should be done after set/delete/activate/deactivate. + * note: if a node is changed, all of its ancestors are also considered + * changed. + * return true if successful. otherwise return false. + */ +bool +Cstore::markCfgPathChanged(const vector& path_comps) +{ + // first mark the root changed + if (!mark_changed()) { + return false; + } + + // now mark each level as changed + vector ppath; + for (unsigned int i = 0; i < path_comps.size(); i++) { + ppath.push_back(path_comps[i]); + if (!cfg_path_exists(ppath, false, false)) { + // this level no longer in working. nothing further. + break; + } + SAVE_PATHS; + append_cfg_path(ppath); + bool ret = mark_changed(); + RESTORE_PATHS; + if (!ret) { + return false; + } + } + return true; +} + + +////// protected functions +void +Cstore::output_user(const char *fmt, ...) +{ + va_list alist; + va_start(alist, fmt); + if (out_stream) { + vfprintf(out_stream, fmt, alist); + } else { + vprintf(fmt, alist); + } + va_end(alist); +} + +void +Cstore::output_internal(const char *fmt, ...) +{ + va_list alist; + va_start(alist, fmt); + + int fdout = -1; + FILE *fout = NULL; + do { + // XXX for now use the constant from cli_val.h + if ((fdout = open(LOGFILE_STDOUT, O_WRONLY | O_CREAT, 0660)) == -1) { + break; + } + if (lseek(fdout, 0, SEEK_END) == ((off_t) -1)) { + break; + } + if ((fout = fdopen(fdout, "a")) == NULL) { + break; + } + vfprintf(fout, fmt, alist); + } while (0); + va_end(alist); + if (fout) { + fclose(fout); + // fdout is implicitly closed + } else if (fdout >= 0) { + close(fdout); + } +} + + +////// private functions +/* try to append the logical path to template path. + * is_tag: (output) whether the last component is a "tag". + * return false if logical path is not valid. otherwise return true. + */ +bool +Cstore::append_tmpl_path(const vector& path_comps, bool& is_tag) +{ + is_tag = false; + for (unsigned int i = 0; i < path_comps.size(); i++) { + push_tmpl_path(path_comps[i]); + if (tmpl_node_exists()) { + // got exact match. continue to next component. + continue; + } + // not exact match. check if it's a tag. + pop_tmpl_path(); + push_tmpl_path_tag(); + if (tmpl_node_exists()) { + // got tag match. continue to next component. + if (i == (path_comps.size() - 1)) { + // last comp + is_tag = true; + } + continue; + } + // not a valid path + return false; + } + return true; +} + +/* check whether specified "logical path" is valid template path. + * then template at the path is parsed. + * path_comps: vector of path components. + * validate_vals: whether to validate all "values" along specified path. + * def: (output) parsed template. + * error: (output) error message if failed. + * return false if invalid template path. otherwise return true. + * note: + * also, if last path component is value (i.e., def.is_value), the template + * parsed is actually at "full path - 1". + */ +bool +Cstore::get_parsed_tmpl(const vector& path_comps, bool validate_vals, + vtw_def& def, string& error) +{ + // default error message + error = "The specified configuration node is not valid"; + + if (tmpl_path_at_root() && path_comps.size() == 0) { + // empty path not valid + return false; + } + + /* note: this function may be invoked recursively (depth 1) when + * validating values, i.e., validate_value will process variable + * reference, which calls this indirectly to get templates. + * so need special save/restore identifier. + */ + const char *not_validating = "get_parsed_tmpl_not_validating"; + if (validate_vals) { + SAVE_PATHS; + } else { + save_paths(not_validating); + } + /* need at least 1 comp to work. 2 comps if last comp is value. + * so pop tmpl_path and prepend them. note that path_comps remain + * constant. + */ + vector *pcomps = const_cast *>(&path_comps); + vector new_path_comps; + if (path_comps.size() < 2) { + new_path_comps = path_comps; + pcomps = &new_path_comps; + for (unsigned int i = 0; i < 2 && pcomps->size() < 2; i++) { + if (!tmpl_path_at_root()) { + pcomps->insert(pcomps->begin(), pop_tmpl_path()); + } + } + } + bool ret = false; + do { + /* cases for template path: + * (1) valid path ending in "actual node", i.e., typeless node, tag node, + * single-value node, or multi-value node: + * => tmpl at full path. e.g.: + * typeless node: "service ssh allow-root" + * tag node: "interfaces ethernet" + * single-value node: "system gateway-address" + * multi-value node: "system name-server" + * (2) valid path ending in "value", i.e., tag (value of tag node), or + * value of single-/multi-value node: + * => tmpl at "full path - 1". e.g.: + * "value" of tag node: "interfaces ethernet eth0" + * value of single-value node: "system gateway-address 1.1.1.1" + * value of multi-value node: "system name-server 2.2.2.2" + * (3) invalid path + * => no tmpl + */ + // first scan up to "full path - 1" + bool valid = true; + for (unsigned int i = 0; i < (pcomps->size() - 1); i++) { + if ((*pcomps)[i] == "") { + // only the last component is potentially allowed to be empty str + valid = false; + break; + } + bool is_tag; + if (append_tmpl_path((*pcomps)[i], is_tag)) { + if (is_tag && validate_vals) { + /* last comp is tag and want to validate value. + * note: validate_val() will use the current tmpl path and cfg path. + * so need both at the "node" level before calling it. + * at this point cfg path is not pushed yet so no need to + * pop it. + */ + pop_tmpl_path(); + if (!validate_val(NULL, (*pcomps)[i])) { + // invalid value + error = "Value validation failed"; + valid = false; + break; + } + // restore tmpl path + append_tmpl_path((*pcomps)[i], is_tag); + } + /* cfg path is not used here but is needed by validate_val(), so + * need to keep it in sync with tmpl path. + */ + push_cfg_path((*pcomps)[i]); + } else { + // not a valid path + valid = false; + break; + } + } + if (!valid) { + // case (3) + break; + } + /* we are valid up to "full path - 1". now process last path component. + * first, if we are case (2), we should find a "value node" at this point. + * note: this is only possible if there are more than 1 component. otherwise + * we haven't done anything yet. + */ + if (pcomps->size() > 1) { + if (tmpl_parse(def)) { + if (def.tag || def.multi || def.def_type != ERROR_TYPE) { + // case (2). last component is "value". + if (validate_vals) { + // validate value + if (!validate_val(&def, (*pcomps)[pcomps->size() - 1])) { + // invalid value + error = "Value validation failed"; + break; + } + } + def.is_value = 1; + ret = true; + break; + } + } + // if no valid template or not a value, it's not case (2) so continue. + } + // now check last component + if ((*pcomps)[pcomps->size() - 1] == "") { + // only value is potentially allowed to be empty str + break; + } + push_tmpl_path((*pcomps)[pcomps->size() - 1]); + // no need to push cfg path (only needed for validate_val()) + if (tmpl_node_exists()) { + // case (1). last component is "node". + if (!tmpl_parse(def)) { + exit_internal("failed to parse tmpl [%s]\n", + tmpl_path_to_str().c_str()); + } + def.is_value = 0; + ret = true; + break; + } + // case (3) (fall through) + } while (0); + if (validate_vals) { + RESTORE_PATHS; + } else { + restore_paths(not_validating); + } + return ret; +} + +/* check if specified "logical path" is valid for "activate" or + * "deactivate" operation. + * return true if valid. otherwise return false. + */ +bool +Cstore::validate_act_deact(const vector& path_comps, const string& op, + vtw_def& def) +{ + string terr; + if (!get_parsed_tmpl(path_comps, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + if (def.is_value && !def.tag) { + /* last component is a value of a single- or multi-value node (i.e., + * a leaf value) => not allowed + */ + output_user("Cannot %s a leaf configuration value\n", op.c_str()); + return false; + } + if (!cfg_path_exists(path_comps, false, true)) { + output_user("Nothing to %s (the specified %s does not exist)\n", + op.c_str(), (!def.is_value || def.tag) ? "node" : "value"); + return false; + } + return true; +} + +/* check if specified args is valid for "rename" or "copy" operation. + * return true if valid. otherwise return false. + */ +bool +Cstore::validate_rename_copy(const vector& args, const string& op) +{ + if (args.size() != 5 || args[2] != "to") { + output_user("Invalid %s command\n", op.c_str()); + return false; + } + string otagnode = args[0]; + string otagval = args[1]; + string ntagnode = args[3]; + string ntagval = args[4]; + if (otagnode != ntagnode) { + output_user("Cannot %s from \"%s\" to \"%s\"\n", + op.c_str(), otagnode.c_str(), ntagnode.c_str()); + return false; + } + + // check the old path + vector ppath; + ppath.push_back(otagnode); + ppath.push_back(otagval); + vtw_def def; + string terr; + if (!get_parsed_tmpl(ppath, false, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + if (!def.is_value || !def.tag) { + // can only rename "tagnode tagvalue" + output_user("Cannot %s under \"%s\"\n", op.c_str(), otagnode.c_str()); + return false; + } + if (!cfg_path_exists(ppath, false, true)) { + output_user("Configuration \"%s %s\" does not exist\n", + otagnode.c_str(), otagval.c_str()); + return false; + } + + // check the new path + ppath.pop_back(); + ppath.push_back(ntagval); + if (cfg_path_exists(ppath, false, true)) { + output_user("Configuration \"%s %s\" already exists\n", + ntagnode.c_str(), ntagval.c_str()); + return false; + } + if (!get_parsed_tmpl(ppath, true, def, terr)) { + output_user("%s\n", terr.c_str()); + return false; + } + return true; +} + +// convert args for "move" to be used for equivalent "rename" operation +bool +Cstore::conv_move_args_for_rename(const vector& args, + vector& edit_path_comps, + vector& rn_args) +{ + /* note: + * "move interfaces ethernet eth2 vif 100 to 200" + * is equivalent to + * "edit interfaces ethernet eth2" + "rename vif 100 to vif 200" + * + * set the extra levels and then just validate as rename + */ + unsigned int num_args = args.size(); + if (num_args < 4) { + // need at least 4 args + return false; + } + for (unsigned int i = 0; i < (num_args - 4); i++) { + edit_path_comps.push_back(args[i]); + } + rn_args.push_back(args[num_args - 4]); // vif + rn_args.push_back(args[num_args - 3]); // 100 + rn_args.push_back(args[num_args - 2]); // to + rn_args.push_back(args[num_args - 4]); // vif + rn_args.push_back(args[num_args - 1]); // 200 + return true; +} + +/* check if specified "logical path" exists in working config or + * active config. + * v_def: ptr to parsed template. NULL if none. + * return true if it exists. otherwise return false. + */ +bool +Cstore::cfg_path_exists(const vector& path_comps, + bool active_cfg, bool include_deactivated) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + // first check if it's a "node". + bool ret = cfg_node_exists(active_cfg); + if (!ret) { + // doesn't exist as a node. maybe a value? + pop_cfg_path(); + ret = cfg_value_exists(path_comps[path_comps.size() - 1], active_cfg); + } + RESTORE_PATHS; + if (ret && !include_deactivated + && cfgPathDeactivated(path_comps, active_cfg)) { + // don't include deactivated + ret = false; + } + return ret; +} + +/* remove tag at current work path and its subtree. + * if specified tag is the last one, also remove the tag node. + * return true if successful. otherwise return false. + * note: assume current work path is a tag. + */ +bool +Cstore::remove_tag() +{ + if (!remove_node()) { + return false; + } + + // go up one level and check if that was the last tag + bool ret = true; + string c = pop_cfg_path(); + vector cnodes; + // get child nodes, including deactivated ones. + get_all_child_node_names(cnodes, false, true); + if (cnodes.size() == 0) { + // it was the last tag. remove the node as well. + if (!remove_node()) { + ret = false; + } + } + push_cfg_path(c); + return ret; +} + +/* remove specified value from the multi-value node at current work path. + * if specified value is the last one, also remove the multi-value node. + * return true if successful. otherwise return false. + * note: assume current work path is a multi-value node and specified value is + * configured for the node. + */ +bool +Cstore::remove_value_from_multi(const string& value) +{ + // get current values + vector vvec; + if (!read_value_vec(vvec, false)) { + return false; + } + + // remove the value + unsigned int bc = vvec.size(); + vector nvec(vvec.begin(), remove(vvec.begin(), vvec.end(), value)); + unsigned int ac = nvec.size(); + + // sanity check + if (ac == bc) { + // nothing removed + return false; + } + if (ac == 0) { + // was the last value. remove the node. + return remove_node(); + } else { + // write the new values out + return write_value_vec(nvec); + } +} + +/* check whether specified value exists at current work path. + * note: assume current work path is a value node. + */ +bool +Cstore::cfg_value_exists(const string& value, bool active_cfg) +{ + // get current values + vector vvec; + if (!read_value_vec(vvec, active_cfg)) { + return false; + } + + return (find(vvec.begin(), vvec.end(), value) != vvec.end()); +} + +/* validate value at current template path. + * def: pointer to parsed template. NULL if none. + * val: value to be validated. + * return true if valid. otherwise return false. + * note: current template and cfg paths both point to the node, + * not the value. + */ +bool +Cstore::validate_val(const vtw_def *def, const string& value) +{ + vtw_def ndef; + if (!def) { + if (!tmpl_parse(ndef)) { + exit_internal("failed to parse tmpl [%s]\n", tmpl_path_to_str().c_str()); + } + def = &ndef; + if (def->def_type == ERROR_TYPE) { + // not a value node + exit_internal("validating non-value node [%s]\n", + tmpl_path_to_str().c_str()); + } + } + + // validate_value() may change "value". make a copy first. + size_t vlen = value.size(); + char *vbuf = (char *) malloc(vlen + 1); + strncpy(vbuf, value.c_str(), vlen + 1); + vbuf[vlen] = 0; + bool ret = validate_val_impl((vtw_def *) def, vbuf); + free(vbuf); + return ret; +} + +/* add tag at current work path. + * return true if successful. otherwise return false. + * note: assume current work path is a new tag and path from root to parent + * already exists. + */ +bool +Cstore::add_tag(const vtw_def& def) +{ + string t = pop_cfg_path(); + vector cnodes; + // get child nodes, excluding deactivated ones. + get_all_child_node_names(cnodes, false, false); + bool ret = false; + do { + if (def.def_tag > 0 && def.def_tag <= cnodes.size()) { + // limit exceeded + output_user("Cannot set node \"%s\": number of values exceeds limit" + "(%d allowed)\n", t.c_str(), def.def_tag); + break; + } + /* XXX the following is the original logic, which is wrong since def_tag + * is unsigned. + */ + if (def.def_tag < 0 && cnodes.size() == 1) { + /* XXX special case in the original implementation where the previous + * tag should be replaced. this is probably unnecessary since + * "rename" can be used for tag node anyway. + */ + ret = rename_child_node(cnodes[0], t); + break; + } + // neither of the above. just add the tag. + ret = add_child_node(t); + } while (0); + push_cfg_path(t); + return ret; +} + +/* add specified value to the multi-value node at current work path. + * return true if successful. otherwise return false. + * note: assume current work path is a multi-value node and specified value is + * not configured for the node. + */ +bool +Cstore::add_value_to_multi(const vtw_def& def, const string& value) +{ + // get current values + vector vvec; + // ignore return value here. if it failed, vvec is empty. + read_value_vec(vvec, false); + + /* note: XXX the original limit-checking logic uses the same count as tag + * node, which is wrong since multi-node values are not stored as + * directories in the original implementation. + * + * also, original logic only applies limit when def_multi > 1. + * this was probably to support the special case in the design + * when def_multi is 1 to make it behave like a single-value + * node (i.e., a subsequent set replaces the value). however, + * the implementation uses "-1" as the special case (but def_multi + * is unsigned anyway). see also "add_tag()". + * + * for now just apply the limit for anything >= 1. + */ + if (def.def_multi >= 1 && vvec.size() >= def.def_multi) { + // limit exceeded + output_user("Cannot set value \"%s\": number of values exceeded " + "(%d allowed)\n", value.c_str(), def.def_multi); + return false; + } + + // append the value + vvec.push_back(value); + return write_value_vec(vvec); +} + +/* this uses the get_all_child_node_names_impl() from the underlying + * implementation but provides the option to exclude deactivated nodes. + */ +void +Cstore::get_all_child_node_names(vector& cnodes, bool active_cfg, + bool include_deactivated) +{ + vector nodes; + get_all_child_node_names_impl(nodes, active_cfg); + for (unsigned int i = 0; i < nodes.size(); i++) { + if (!include_deactivated) { + push_cfg_path(nodes[i]); + bool skip = marked_deactivated(active_cfg); + pop_cfg_path(); + if (skip) { + continue; + } + } + cnodes.push_back(nodes[i]); + } +} + +/* create all child nodes of current work path that have default values + * return true if successful. otherwise return false. + * note: assume current work path has just been created so no child + * nodes exist. + */ +bool +Cstore::create_default_children() +{ + vector tcnodes; + get_all_tmpl_child_node_names(tcnodes); + bool ret = true; + for (unsigned int i = 0; i < tcnodes.size(); i++) { + push_tmpl_path(tcnodes[i]); + vtw_def def; + if (tmpl_node_exists() && tmpl_parse(def)) { + if (def.def_default) { + // has default value. set it. + push_cfg_path(tcnodes[i]); + if (!add_node() || !write_value(def.def_default) + || !mark_display_default()) { + ret = false; + } + pop_cfg_path(); + } + } + pop_tmpl_path(); + if (!ret) { + break; + } + } + return ret; +} + +/* return environment string for "edit"-related operations based on current + * work/tmpl paths. + */ +void +Cstore::get_edit_env(string& env) +{ + vector lvec; + get_edit_level(lvec); + string lvl; + for (unsigned int i = 0; i < lvec.size(); i++) { + if (lvl.length() > 0) { + lvl += " "; + } + lvl += lvec[i]; + } + + env = ("export " + C_ENV_EDIT_LEVEL + "='" + get_edit_level_path() + "';"); + env += (" export " + C_ENV_TMPL_LEVEL + "='" + get_tmpl_level_path() + "';"); + env += (" export " + C_ENV_SHELL_PROMPT + "='" + + get_shell_prompt(lvl) + "';"); +} + +// return shell prompt string for specified edit level +string +Cstore::get_shell_prompt(const string& level) +{ + string lvl = level; + if (lvl.length() > 0) { + lvl = " " + lvl; + } + return ("[edit" + lvl + "]\\n\\u@\\h# "); +} + +// escape the single quotes in the string for shell +void +Cstore::shell_escape_squotes(string& str) +{ + size_t sq = 0; + while ((sq = str.find('\'', sq)) != str.npos) { + str.replace(sq, 1, "'\\''"); + sq += 4; + } +} + diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp new file mode 100644 index 0000000..f6a4215 --- /dev/null +++ b/src/cstore/cstore.hpp @@ -0,0 +1,409 @@ +/* + * Copyright (C) 2010 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 . + */ + +#ifndef _CSTORE_H_ +#define _CSTORE_H_ +#include +#include +#include + +extern "C" { +#include +#include +} + +#define exit_internal(fmt, args...) do \ + { \ + output_internal("[%s:%d] " fmt, __FILE__, __LINE__ , ##args); \ + exit(-1); \ + } while (0); + +/* macros for saving/restoring paths. + * note: this allows "nested" save/restore invocations but NOT recursive ones. + */ +#define SAVE_PATHS save_paths(&__func__) +#define RESTORE_PATHS restore_paths(&__func__) + +using namespace std; + +class Cstore { +public: + Cstore() {}; + Cstore(string& env); + ~Cstore() {}; + + // constants + static const string C_NODE_STATUS_DELETED; + static const string C_NODE_STATUS_ADDED; + static const string C_NODE_STATUS_CHANGED; + static const string C_NODE_STATUS_STATIC; + + static const string C_ENV_EDIT_LEVEL; + static const string C_ENV_TMPL_LEVEL; + + static const string C_ENV_SHELL_PROMPT; + static const string C_ENV_SHELL_CWORDS; + static const string C_ENV_SHELL_CWORD_COUNT; + + static const string C_ENV_SHAPI_COMP_VALS; + static const string C_ENV_SHAPI_LCOMP_VAL; + static const string C_ENV_SHAPI_COMP_HELP; + static const string C_ENV_SHAPI_HELP_ITEMS; + static const string C_ENV_SHAPI_HELP_STRS; + + static const string C_ENUM_SCRIPT_DIR; + + static const size_t MAX_CMD_OUTPUT_SIZE = 4096; + + ////// the public cstore interface + //// functions implemented in this base class + // these operate on template path + bool validateTmplPath(const vector& path_comps, bool validate_vals); + bool validateTmplPath(const vector& path_comps, bool validate_vals, + vtw_def& def); + bool getParsedTmpl(const vector& path_comps, + map& tmap, bool allow_val = true); + void tmplGetChildNodes(const vector& path_comps, + vector& cnodes); + + /****** + * functions for actual CLI operations: + * set + * delete + * activate + * deactivate + * rename + * copy + * comment + * discard + * move (currently this is only available in cfg-cmd-wrapper) + * edit-related commands (invoked from shell functions) + * completion-related (for completion script) + * session-related (setup, teardown, etc.) + * load (XXX currently still implemented in perl) + * + * these operate on the "working config" and the session and MUST NOT + * be used by anything other than the listed operations. + */ + // set + bool validateSetPath(const vector& path_comps); + bool setCfgPath(const vector& path_comps); + // delete + bool validateDeletePath(const vector& path_comps, vtw_def& def); + bool deleteCfgPath(const vector& path_comps, const vtw_def& def); + // activate (actually "unmark deactivated" since it is 2-state, not 3) + bool validateActivatePath(const vector& path_comps); + bool unmarkCfgPathDeactivated(const vector& path_comps); + // deactivate + bool validateDeactivatePath(const vector& path_comps); + bool markCfgPathDeactivated(const vector& path_comps); + // rename + bool validateRenameArgs(const vector& args); + bool renameCfgPath(const vector& args); + // copy + bool validateCopyArgs(const vector& args); + bool copyCfgPath(const vector& args); + // comment + bool validateCommentArgs(const vector& args, vtw_def& def); + bool commentCfgPath(const vector& args, const vtw_def& def); + // discard + bool discardChanges(); + // move + bool validateMoveArgs(const vector& args); + bool moveCfgPath(const vector& args); + // edit-related + bool getEditEnv(const vector& path_comps, string& env); + bool getEditUpEnv(string& env); + bool getEditResetEnv(string& env); + bool editLevelAtRoot() { + return edit_level_at_root(); + }; + // completion-related + bool getCompletionEnv(const vector& comps, string& env); + void getEditLevel(vector& comps) { + get_edit_level(comps); + }; + // session-related + virtual bool markSessionUnsaved() = 0; + virtual bool unmarkSessionUnsaved() = 0; + virtual bool sessionUnsaved() = 0; + virtual bool sessionChanged() = 0; + virtual bool setupSession() = 0; + virtual bool teardownSession() = 0; + virtual bool inSession() = 0; + // common + bool markCfgPathChanged(const vector& path_comps); + // XXX load + //bool unmarkCfgPathDeactivatedDescendants(const vector& path_comps); + + /****** + * these functions are observers of the current "working config" or + * "active config" during a config session. + * MOST users of the cstore API should be using these functions (most + * likely during a commit operation). + * + * note that these MUST NOT worry about "deactivated" state. + * for these functions, deactivated nodes are equivalent to having been + * deleted. in other words, these functions are NOT "deactivate-aware". + * + * also, the functions that can be used to observe "active config" can + * be used outside a config session as well (only when observing active + * config, of course). + */ + // observers for "working config" (by default) OR "active config" + bool cfgPathExists(const vector& path_comps, + bool active_cfg = false); + void cfgPathGetChildNodes(const vector& path_comps, + vector& cnodes, bool active_cfg = false); + bool cfgPathGetValue(const vector& path_comps, string& value, + bool active_cfg = false); + bool cfgPathGetValues(const vector& path_comps, + vector& values, bool active_cfg = false); + bool cfgPathGetComment(const vector& path_comps, string& comment, + bool active_cfg = false); + /* observers for working AND active configs (at the same time). + * MUST ONLY be used during config session. + */ + bool cfgPathDeleted(const vector& path_comps); + bool cfgPathAdded(const vector& path_comps); + bool cfgPathChanged(const vector& path_comps); + void cfgPathGetDeletedChildNodes(const vector& path_comps, + vector& cnodes); + void cfgPathGetChildNodesStatus(const vector& path_comps, + map& cmap); + /* observers for "effective config" (a combination of working config, + * active config, and commit processing state) only. + * MUST ONLY be used during config session. + */ + bool cfgPathEffective(const vector& path_comps); + void cfgPathGetEffectiveChildNodes(const vector& path_comps, + vector& cnodes); + bool cfgPathGetEffectiveValue(const vector& path_comps, + string& value); + bool cfgPathGetEffectiveValues(const vector& path_comps, + vector& values); + + /****** + * "deactivate-aware" observers of the current working or active config. + * these are the only functions that are allowed to see the "deactivate" + * state of config nodes. + * + * these functions MUST ONLY be used by operations that NEED to distinguish + * between deactivated nodes and deleted nodes. below is the list + * of operations that are allowed to use these functions: + * * configuration output (show, save, load) + * + * operations that are not on the above list MUST NOT use these + * "deactivate-aware" functions. + * + * note: the last argument "include_deactivated" for the DA functions + * is for implementation convenience and does not need to be + * passed in when calling them. + */ + // working or active config + bool cfgPathDeactivated(const vector& path_comps, + bool active_cfg = false); + bool cfgPathMarkedDeactivated(const vector& path_comps, + bool active_cfg = false); + bool cfgPathExistsDA(const vector& path_comps, + bool active_cfg = false, + bool include_deactivated = true); + void cfgPathGetChildNodesDA(const vector& path_comps, + vector& cnodes, + bool active_cfg = false, + bool include_deactivated = true); + bool cfgPathGetValueDA(const vector& path_comps, string& value, + bool active_cfg = false, + bool include_deactivated = true); + bool cfgPathGetValuesDA(const vector& path_comps, + vector& values, + bool active_cfg = false, + bool include_deactivated = true); + // working AND active configs + void cfgPathGetDeletedChildNodesDA(const vector& path_comps, + vector& cnodes, + bool include_deactivated = true); + void cfgPathGetChildNodesStatusDA(const vector& path_comps, + map& cmap); + + + /* these are internal API functions and operate on current cfg and + * tmpl paths during cstore operations. they are only used to work around + * the limitations of the original CLI library implementation and MUST NOT + * be used by anyone other than the original CLI library. + */ + bool getVarRef(const string& ref_str, clind_val& cval, bool from_active); + bool setVarRef(const string& ref_str, const string& value, bool to_active); + +protected: + ////// functions for subclasses + void output_user(const char *fmt, ...); + void output_internal(const char *fmt, ...); + +private: + ////// member class + // for variable reference + class VarRef; + + ////// virtual + /* "path modifiers" + * note: only these functions are allowed to permanently change the paths. + * all other functions must "preserve" the paths (but can change paths + * "during" an invocation), i.e., the paths after invocation must be + * the same as before invocation. + */ + // begin path modifiers + virtual void push_tmpl_path(const string& path_comp) = 0; + virtual void push_tmpl_path_tag() = 0; + virtual string pop_tmpl_path() = 0; + virtual void push_cfg_path(const string& path_comp) = 0; + virtual void push_cfg_path_val() = 0; + virtual string pop_cfg_path() = 0; + virtual void append_cfg_path(const vector& path_comps) = 0; + virtual void reset_paths() = 0; + virtual void save_paths(const void *handle = NULL) = 0; + virtual void restore_paths(const void *handle = NULL) = 0; + virtual bool cfg_path_at_root() = 0; + virtual bool tmpl_path_at_root() = 0; + // end path modifiers + + // these operate on current tmpl path + virtual bool tmpl_node_exists() = 0; + virtual bool tmpl_parse(vtw_def& def) = 0; + + // these operate on current work path (or active with "active_cfg") + virtual bool remove_node() = 0; + virtual void get_all_child_node_names_impl(vector& cnodes, + bool active_cfg = false) = 0; + virtual void get_all_tmpl_child_node_names(vector& cnodes) = 0; + virtual bool write_value_vec(const vector& vvec, + bool active_cfg = false) = 0; + virtual bool add_node() = 0; + virtual bool rename_child_node(const string& oname, const string& nname) = 0; + virtual bool copy_child_node(const string& oname, const string& nname) = 0; + virtual bool mark_display_default() = 0; + virtual bool unmark_display_default() = 0; + virtual bool mark_deactivated() = 0; + virtual bool unmark_deactivated() = 0; + virtual bool unmark_deactivated_descendants() = 0; + virtual bool mark_changed() = 0; + virtual bool remove_comment() = 0; + virtual bool set_comment(const string& comment) = 0; + virtual bool discard_changes(unsigned long long& num_removed) = 0; + + // observers for current work path + virtual bool marked_changed() = 0; + virtual bool marked_display_default() = 0; + + // observers for current work path or active path + virtual bool read_value_vec(vector& vvec, bool active_cfg) = 0; + virtual bool cfg_node_exists(bool active_cfg) = 0; + virtual bool marked_deactivated(bool active_cfg) = 0; + virtual bool get_comment(string& comment, bool active_cfg) = 0; + + // observers during commit operation + virtual bool marked_committed(const vtw_def& def, bool is_set) = 0; + + // these operate on both current tmpl and work paths + virtual bool validate_val_impl(vtw_def *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 + * of these "levels" environment vars require filesystem-specific + * "escapes", so handle them in derived class. + * + * revisit when the env vars are redefined. + * + * these operate on current "work" or "tmpl" path, i.e., cfg/tmpl path + * needs to be set up before calling these. + */ + virtual string get_edit_level_path() = 0; + virtual string get_tmpl_level_path() = 0; + virtual void get_edit_level(vector& path_comps) = 0; + virtual bool edit_level_at_root() = 0; + + // these are for testing/debugging + virtual string cfg_path_to_str() = 0; + virtual string tmpl_path_to_str() = 0; + + ////// implemented + // begin path modifiers (only these can change path permanently) + bool append_tmpl_path(const vector& path_comps, bool& is_tag); + bool append_tmpl_path(const vector& path_comps) { + bool dummy; + return append_tmpl_path(path_comps, dummy); + }; + bool append_tmpl_path(const string& path_comp, bool& is_tag) { + return append_tmpl_path(vector(1, path_comp), is_tag); + }; + bool append_tmpl_path(const string& path_comp) { + bool dummy; + return append_tmpl_path(path_comp, dummy); + }; + // end path modifiers + + // these require full path + // (note: get_parsed_tmpl also uses current tmpl path) + bool get_parsed_tmpl(const vector& path_comps, bool validate_vals, + vtw_def& def, string& error); + bool get_parsed_tmpl(const vector& path_comps, bool validate_vals, + vtw_def& def) { + string dummy; + return get_parsed_tmpl(path_comps, validate_vals, def, dummy); + }; + bool validate_act_deact(const vector& path_comps, const string& op, + vtw_def& def); + bool validate_rename_copy(const vector& args, const string& op); + bool conv_move_args_for_rename(const vector& args, + vector& edit_path_comps, + vector& rn_args); + bool cfg_path_exists(const vector& path_comps, + bool active_cfg, bool include_deactivated); + + // these operate on current work path (or active with "active_cfg") + bool remove_tag(); + bool remove_value_from_multi(const string& value); + bool write_value(const string& value, bool active_cfg = false) { + vector vvec(1, value); + return write_value_vec(vvec, active_cfg); + }; + bool add_tag(const vtw_def& def); + bool add_value_to_multi(const vtw_def& def, const string& value); + bool add_child_node(const string& name) { + push_cfg_path(name); + bool ret = add_node(); + pop_cfg_path(); + return ret; + }; + void get_all_child_node_names(vector& cnodes, bool active_cfg, + bool include_deactivated); + + // observers for work path or active path + bool cfg_value_exists(const string& value, bool active_cfg); + + // these operate on both current tmpl and work paths + bool validate_val(const vtw_def *def, const string& value); + bool create_default_children(); + void get_edit_env(string& env); + + // util functions + string get_shell_prompt(const string& level); + void shell_escape_squotes(string& str); +}; + +#endif /* _CSTORE_H_ */ + diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp new file mode 100644 index 0000000..977a597 --- /dev/null +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -0,0 +1,1078 @@ +/* + * Copyright (C) 2010 Vyatta, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +extern "C" { +#include "cli_val.h" +#include "cli_objects.h" +} + +#include "cstore-unionfs.hpp" + + +////// constants +// environment vars defining root dirs +const string UnionfsCstore::C_ENV_TMPL_ROOT = "VYATTA_CONFIG_TEMPLATE"; +const string UnionfsCstore::C_ENV_WORK_ROOT = "VYATTA_TEMP_CONFIG_DIR"; +const string UnionfsCstore::C_ENV_ACTIVE_ROOT + = "VYATTA_ACTIVE_CONFIGURATION_DIR"; +const string UnionfsCstore::C_ENV_CHANGE_ROOT = "VYATTA_CHANGES_ONLY_DIR"; +const string UnionfsCstore::C_ENV_TMP_ROOT = "VYATTA_CONFIG_TMP"; + +// default root dirs/paths +const string UnionfsCstore::C_DEF_TMPL_ROOT + = "/opt/vyatta/share/vyatta-cfg/templates"; +const string UnionfsCstore::C_DEF_CFG_ROOT + = "/opt/vyatta/config"; +const string UnionfsCstore::C_DEF_ACTIVE_ROOT + = UnionfsCstore::C_DEF_CFG_ROOT + "/active"; +const string UnionfsCstore::C_DEF_CHANGE_PREFIX = "/tmp/changes_only_"; +const string UnionfsCstore::C_DEF_WORK_PREFIX + = UnionfsCstore::C_DEF_CFG_ROOT + "/tmp/new_config_"; +const string UnionfsCstore::C_DEF_TMP_PREFIX + = UnionfsCstore::C_DEF_CFG_ROOT + "/tmp/tmp_"; + +// markers +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_COMMENT_FILE = ".comment"; + + +////// static +static map _fs_escape_chars; +static map _fs_unescape_chars; +static void +_init_fs_escape_chars() +{ + _fs_escape_chars[-1] = "\%\%\%"; + _fs_escape_chars['%'] = "\%25"; + _fs_escape_chars['/'] = "\%2F"; + + _fs_unescape_chars["\%\%\%"] = -1; + _fs_unescape_chars["\%25"] = '%'; + _fs_unescape_chars["\%2F"] = '/'; +} + +static string +_escape_char(char c) +{ + map::iterator p = _fs_escape_chars.find(c); + if (p != _fs_escape_chars.end()) { + return _fs_escape_chars[c]; + } else { + return string(1, c); + } +} + +static map _escape_path_name_cache; + +static string +_escape_path_name(const string& path) +{ + map::iterator p = _escape_path_name_cache.find(path); + if (p != _escape_path_name_cache.end()) { + // found escaped string in cache. just return it. + return _escape_path_name_cache[path]; + } + + // special case for empty string + string npath = (path.size() == 0) ? _fs_escape_chars[-1] : ""; + for (unsigned int i = 0; i < path.size(); i++) { + npath += _escape_char(path[i]); + } + + // cache it before return + _escape_path_name_cache[path] = npath; + return npath; +} + +static map _unescape_path_name_cache; + +static string +_unescape_path_name(const string& path) +{ + map::iterator p = _unescape_path_name_cache.find(path); + if (p != _unescape_path_name_cache.end()) { + // found unescaped string in cache. just return it. + return _unescape_path_name_cache[path]; + } + + // assume all escape patterns are 3-char + string npath = ""; + for (unsigned int i = 0; i < path.size(); i++) { + if ((path.size() - i) < 3) { + npath += path.substr(i); + break; + } + string s = path.substr(i, 3); + map::iterator p = _fs_unescape_chars.find(s); + if (p != _fs_unescape_chars.end()) { + char c = _fs_unescape_chars[s]; + if (path.size() == 3 && c == -1) { + // special case for empty string + npath = ""; + break; + } + npath += string(1, _fs_unescape_chars[s]); + // skip the escape sequence + i += 2; + } else { + npath += path.substr(i, 1); + } + } + // cache it before return + _unescape_path_name_cache[path] = npath; + return npath; +} + + +////// constructor/destructor +/* "current session" constructor. + * this constructor sets up the object from environment. + * used when environment is already set up, i.e., when operating on the + * "current" config session. e.g., in the following scenarios + * configure commands + * perl module + * shell "current session" api + * + * note: this also applies when using the cstore in operational mode, + * in which case only the template root and the active root will be + * valid. + */ +UnionfsCstore::UnionfsCstore(bool use_edit_level) +{ + // set up root dir strings + char *val; + if ((val = getenv(C_ENV_TMPL_ROOT.c_str()))) { + tmpl_path = val; + } else { + tmpl_path = C_DEF_TMPL_ROOT; + } + tmpl_root = tmpl_path; // save a copy of tmpl root + if ((val = getenv(C_ENV_WORK_ROOT.c_str()))) { + work_root = val; + } + if ((val = getenv(C_ENV_TMP_ROOT.c_str()))) { + tmp_root = val; + } + if ((val = getenv(C_ENV_ACTIVE_ROOT.c_str()))) { + active_root = val; + } else { + active_root = C_DEF_ACTIVE_ROOT; + } + if ((val = getenv(C_ENV_CHANGE_ROOT.c_str()))) { + change_root = val; + } + + /* note: the original perl API module does not use the edit levels + * from environment. only the actual CLI operations use them. + * so here make it an option. + */ + if (use_edit_level) { + // set up path strings + if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) { + mutable_cfg_path = val; + } + if ((val = getenv(C_ENV_TMPL_LEVEL.c_str()))) { + tmpl_path /= val; + } + } + _init_fs_escape_chars(); +} + +/* "specific session" constructor. + * this constructor sets up the object for the specified session ID and + * returns an environment string that can be "evaled" to set up the + * shell environment. + * + * used when the session environment needs to be established. this is + * mainly for the shell functions that set up configuration sessions. + * i.e., the "vyatta-cfg-cmd-wrapper" (on boot or for GUI etc.) and + * the cfg completion script (when entering configure mode). + * + * sid: session ID. + * env: (output) environment string. + * + * note: this does NOT set up the session. caller needs to use the + * explicit session setup/teardown functions as needed. + */ +UnionfsCstore::UnionfsCstore(const string& sid, string& env) + : Cstore(env) +{ + tmpl_root = C_DEF_TMPL_ROOT; + tmpl_path = tmpl_root; + active_root = C_DEF_ACTIVE_ROOT; + work_root = (C_DEF_WORK_PREFIX + sid); + change_root = (C_DEF_CHANGE_PREFIX + sid); + tmp_root = (C_DEF_TMP_PREFIX + sid); + + string declr = " declare -x -r "; // readonly vars + env += " umask 002; {"; + env += (declr + C_ENV_ACTIVE_ROOT + "=" + active_root.file_string()); + env += (declr + C_ENV_CHANGE_ROOT + "=" + change_root.file_string() + ";"); + env += (declr + C_ENV_WORK_ROOT + "=" + work_root.file_string() + ";"); + env += (declr + C_ENV_TMP_ROOT + "=" + tmp_root.file_string() + ";"); + env += (declr + C_ENV_TMPL_ROOT + "=" + tmpl_root.file_string() + ";"); + env += " } >&/dev/null || true"; + + // set up path strings using level vars + char *val; + if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) { + mutable_cfg_path = val; + } + if ((val = getenv(C_ENV_TMPL_LEVEL.c_str()))) { + tmpl_path /= val; + } + + _init_fs_escape_chars(); +} + +UnionfsCstore::~UnionfsCstore() +{ +} + + +////// public virtual functions declared in base class +bool +UnionfsCstore::markSessionUnsaved() +{ + b_fs::path marker = work_root / C_MARKER_UNSAVED; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark unsaved [%s]\n", + marker.file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmarkSessionUnsaved() +{ + b_fs::path marker = work_root / C_MARKER_UNSAVED; + if (!b_fs::exists(marker)) { + // not marked. treat as success. + return true; + } + try { + b_fs::remove(marker); + } catch (...) { + output_internal("failed to unmark unsaved [%s]\n", + marker.file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::sessionUnsaved() +{ + b_fs::path marker = work_root / C_MARKER_UNSAVED; + return b_fs::exists(marker); +} + +bool +UnionfsCstore::sessionChanged() +{ + b_fs::path marker = work_root / C_MARKER_CHANGED; + return b_fs::exists(marker); +} + +/* set up the session associated with this object. + * the session comes from either the environment or the session ID + * (see the two different constructors). + */ +bool +UnionfsCstore::setupSession() +{ + if (!b_fs::exists(work_root)) { + // session doesn't exist. create dirs. + try { + b_fs::create_directories(work_root); + b_fs::create_directories(change_root); + b_fs::create_directories(tmp_root); + if (!b_fs::exists(active_root)) { + // this should only be needed on boot + b_fs::create_directories(active_root); + } + } catch (...) { + output_internal("setup session failed to create session directories\n"); + return false; + } + + // union mount + string mopts = ("dirs=" + change_root.file_string() + "=rw:" + + active_root.file_string() + "=ro"); + if (mount("unionfs", work_root.file_string().c_str(), "unionfs", 0, + mopts.c_str()) != 0) { + output_internal("setup session mount failed [%s][%s]\n", + strerror(errno), work_root.file_string().c_str()); + return false; + } + } else if (!b_fs::is_directory(work_root)) { + output_internal("setup session not dir [%s]\n", + work_root.file_string().c_str()); + return false; + } + return true; +} + +/* tear down the session associated with this object. + * the session comes from either the environment or the session ID + * (see the two different constructors). + */ +bool +UnionfsCstore::teardownSession() +{ + // check if session exists + string wstr = work_root.file_string(); + if (wstr.empty() || wstr.find(C_DEF_WORK_PREFIX) != 0 + || !b_fs::exists(work_root) || !b_fs::is_directory(work_root)) { + // no session + output_internal("teardown invalid session [%s]\n", wstr.c_str()); + return false; + } + + // 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()); + return false; + } + + // remove session directories + bool ret = false; + try { + if (b_fs::remove_all(work_root) != 0 + && b_fs::remove_all(change_root) != 0 + && b_fs::remove_all(tmp_root) != 0) { + ret = true; + } + } catch (...) { + } + if (!ret) { + output_internal("failed to remove session directories\n"); + } + return ret; +} + +/* whether an actual config session is associated with this object. + * the session comes from either the environment or the session ID + * (see the two different constructors). + */ +bool +UnionfsCstore::inSession() +{ + string wstr = work_root.file_string(); + return (!wstr.empty() && wstr.find(C_DEF_WORK_PREFIX) == 0 + && b_fs::exists(work_root) && b_fs::is_directory(work_root)); +} + + +////// virtual functions defined in base class +/* check if current tmpl_path is a valid tmpl dir. + * return true if valid. otherwise return false. + */ +bool +UnionfsCstore::tmpl_node_exists() +{ + return (b_fs::exists(tmpl_path) && b_fs::is_directory(tmpl_path)); +} + +/* parse template at current tmpl_path. + * def: for storing parsed template. + * return true if successful. otherwise return false. + */ +bool +UnionfsCstore::tmpl_parse(vtw_def& def) +{ + push_tmpl_path(DEF_NAME); + bool ret = (b_fs::exists(tmpl_path) && b_fs::is_regular(tmpl_path) + && parse_def(&def, tmpl_path.file_string().c_str(), FALSE) == 0); + pop_tmpl_path(); + return ret; +} + +bool +UnionfsCstore::cfg_node_exists(bool active_cfg) +{ + b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); + return (b_fs::exists(p) && b_fs::is_directory(p)); +} + +bool +UnionfsCstore::add_node() +{ + bool ret = true; + try { + if (!b_fs::create_directory(get_work_path())) { + // already exists. shouldn't call this function. + ret = false; + } + } catch (...) { + ret = false; + } + if (!ret) { + output_internal("failed to add node [%s]\n", + get_work_path().file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::remove_node() +{ + if (!b_fs::exists(get_work_path()) || !b_fs::is_directory(get_work_path())) { + output_internal("remove non-existent node [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + bool ret = false; + try { + if (b_fs::remove_all(get_work_path()) != 0) { + ret = true; + } + } catch (...) { + ret = false; + } + if (!ret) { + output_internal("failed to remove node [%s]\n", + get_work_path().file_string().c_str()); + } + return ret; +} + +void +UnionfsCstore::get_all_child_node_names_impl(vector& cnodes, + bool active_cfg) +{ + b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); + get_all_child_dir_names(p, cnodes); + + /* XXX special cases to emulate original perl API behavior. + * original perl listNodes() and listOrigNodes() return everything + * under a node (except for ".*"), including "node.val" and "def". + * + * perl API should operate at abstract level and should not access + * such implementation-specific details. however, currently + * things like config output depend on this behavior, so this + * function needs to return them for now. + * + * use a whilelist-approach, i.e., only add the following: + * node.val + * def + */ + if (b_fs::exists(p / VAL_NAME) && b_fs::is_regular(p / VAL_NAME)) { + cnodes.push_back(VAL_NAME); + } + if (b_fs::exists(p / C_MARKER_DEF_VALUE) + && b_fs::is_regular(p / C_MARKER_DEF_VALUE)) { + cnodes.push_back(C_MARKER_DEF_VALUE); + } +} + +bool +UnionfsCstore::read_value_vec(vector& vvec, bool active_cfg) +{ + push_cfg_path_val(); + b_fs::path vpath = (active_cfg ? get_active_path() : get_work_path()); + bool ret = false; + do { + string ostr; + if (!read_whole_file(vpath, ostr)) { + break; + } + + /* XXX original implementation used to remove a trailing '\n' after + * a read. it was only necessary because it was adding a '\n' when + * writing the file. don't remove anything now since we shouldn't + * be writing it any more. + */ + // separate values using newline as delimiter + unsigned int start_idx = 0, idx = 0; + for (; idx < ostr.size(); idx++) { + if (ostr[idx] == '\n') { + // got a value + vvec.push_back(ostr.substr(start_idx, (idx - start_idx))); + start_idx = idx + 1; + } + } + if (start_idx < ostr.size()) { + vvec.push_back(ostr.substr(start_idx, (idx - start_idx))); + } else { + // last char is a newline => another empty value + vvec.push_back(""); + } + ret = true; + } while (0); + pop_cfg_path(); + return ret; +} + +bool +UnionfsCstore::write_value_vec(const vector& vvec, bool active_cfg) +{ + push_cfg_path_val(); + bool ret = false; + b_fs::path wp = (active_cfg ? get_active_path() : get_work_path()); + do { + if (b_fs::exists(wp) && !b_fs::is_regular(wp)) { + // not a file + break; + } + + string ostr = ""; + for (unsigned int i = 0; i < vvec.size(); i++) { + if (i > 0) { + // subsequent values require delimiter + ostr += "\n"; + } + ostr += vvec[i]; + } + + if (!write_file(wp.file_string().c_str(), ostr)) { + break; + } + ret = true; + } while (0); + pop_cfg_path(); + if (!ret) { + output_internal("failed to write node value [%s]\n", + wp.file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::rename_child_node(const string& oname, const string& nname) +{ + b_fs::path opath = get_work_path() / oname; + b_fs::path npath = get_work_path() / nname; + if (!b_fs::exists(opath) || !b_fs::is_directory(opath) + || b_fs::exists(npath)) { + output_internal("cannot rename node [%s,%s,%s]\n", + get_work_path().file_string().c_str(), + oname.c_str(), nname.c_str()); + return false; + } + bool ret = true; + try { + /* somehow b_fs::rename() can't be used here as it considers the operation + * "Invalid cross-device link" and fails with an exception, probably due + * to unionfs in some way. + * do it the hard way. + */ + recursive_copy_dir(opath, npath); + if (b_fs::remove_all(opath) == 0) { + ret = false; + } + } catch (...) { + ret = false; + } + if (!ret) { + output_internal("failed to rename node [%s,%s]\n", + opath.file_string().c_str(), + npath.file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::copy_child_node(const string& oname, const string& nname) +{ + b_fs::path opath = get_work_path() / oname; + b_fs::path npath = get_work_path() / nname; + if (!b_fs::exists(opath) || !b_fs::is_directory(opath) + || b_fs::exists(npath)) { + output_internal("cannot copy node [%s,%s,%s]\n", + get_work_path().file_string().c_str(), + oname.c_str(), nname.c_str()); + return false; + } + try { + recursive_copy_dir(opath, npath); + } catch (...) { + output_internal("failed to copy node [%s,%s,%s]\n", + get_work_path().file_string().c_str(), + oname.c_str(), nname.c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::mark_display_default() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark default [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmark_display_default() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + if (!b_fs::exists(marker)) { + // not marked. treat as success. + return true; + } + try { + b_fs::remove(marker); + } catch (...) { + output_internal("failed to unmark default [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::marked_display_default() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + return b_fs::exists(marker); +} + +bool +UnionfsCstore::marked_deactivated(bool active_cfg) +{ + b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); + b_fs::path marker = p / C_MARKER_DEACTIVATE; + return b_fs::exists(marker); +} + +bool +UnionfsCstore::mark_deactivated() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark deactivated [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmark_deactivated() +{ + b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE; + if (!b_fs::exists(marker)) { + // not deactivated. treat as success. + return true; + } + try { + b_fs::remove(marker); + } catch (...) { + output_internal("failed to unmark deactivated [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; +} + +bool +UnionfsCstore::unmark_deactivated_descendants() +{ + bool ret = false; + do { + // sanity check + if (!b_fs::is_directory(get_work_path())) { + break; + } + + vector markers; + b_fs::recursive_directory_iterator di(get_work_path()); + for (; di != b_fs::recursive_directory_iterator(); ++di) { + if (!b_fs::is_regular(di->path()) + || di->path().filename() != C_MARKER_DEACTIVATE) { + // not marker + continue; + } + if (di->path().parent_path() == get_work_path()) { + // don't unmark the node itself + continue; + } + markers.push_back(di->path()); + } + try { + for (unsigned int i = 0; i < markers.size(); i++) { + b_fs::remove(markers[i]); + } + } catch (...) { + break; + } + ret = true; + } while (0); + if (!ret) { + output_internal("failed to unmark deactivated descendants [%s]\n", + get_work_path().file_string().c_str()); + } + return ret; +} + +bool +UnionfsCstore::mark_changed() +{ + if (!mutable_cfg_path.has_parent_path()) { + /* at root, mark changed. root marker is needed by the original + * implementation as an indication of whether the whole config + * has changed. + */ + b_fs::path marker = get_work_path() / C_MARKER_CHANGED; + if (b_fs::exists(marker)) { + // already marked. treat as success. + return true; + } + if (!create_file(marker.file_string())) { + output_internal("failed to mark changed [%s]\n", + get_work_path().file_string().c_str()); + return false; + } + return true; + } + + /* XXX not at root => nop for now. + * we should be marking changed here. however, as commit is still + * using its own unionfs implementation, it will not understand the + * markers and therefore will not perform the necessary cleanup when + * it's done. + * + * for now, don't mark anything besides root. the query function + * will use unionfs-specific implementation (changes-only dir). + */ + return true; +} + +// remove the comment at the current work path +bool +UnionfsCstore::remove_comment() +{ + b_fs::path cfile = get_work_path() / C_COMMENT_FILE; + if (!b_fs::exists(cfile)) { + return false; + } + try { + b_fs::remove(cfile); + } catch (...) { + output_internal("failed to remove comment [%s]\n", + cfile.file_string().c_str()); + return false; + } + return true; +} + +// set comment at the current work path +bool +UnionfsCstore::set_comment(const string& comment) +{ + b_fs::path cfile = get_work_path() / C_COMMENT_FILE; + return write_file(cfile.file_string(), comment); +} + +// discard all changes in working config +bool +UnionfsCstore::discard_changes(unsigned long long& num_removed) +{ + // unionfs-specific implementation + vector files; + vector directories; + // iterate through all entries in change root + b_fs::directory_iterator di(change_root); + for (; di != b_fs::directory_iterator(); ++di) { + if (b_fs::is_directory(di->path())) { + directories.push_back(di->path()); + } else { + files.push_back(di->path()); + } + } + + // remove and count + try { + num_removed = 0; + for (unsigned int i = 0; i < files.size(); i++) { + b_fs::remove(files[i]); + num_removed++; + } + for (unsigned int i = 0; i < directories.size(); i++) { + num_removed += b_fs::remove_all(directories[i]); + } + } catch (...) { + output_internal("discard failed [%s]\n", + change_root.file_string().c_str()); + return false; + } + return true; +} + +// get comment at the current work or active path +bool +UnionfsCstore::get_comment(string& comment, bool active_cfg) +{ + b_fs::path cfile = (active_cfg ? get_active_path() : get_work_path()); + cfile /= C_COMMENT_FILE; + return read_whole_file(cfile, comment); +} + +bool +UnionfsCstore::marked_changed() +{ + /* this function is only called by cfgPathChanged() in base class. + * + * XXX currently just use the changes_only dir for this query. + * see explanation in mark_changed(). + * + * this implementation relies on the fact that cfgPathChanged() + * includes deleted/added nodes (including deactivated/activated + * nodes since it's NOT deactivate-aware). if that is not the case, + * result will be different between deleted nodes (NOT IN + * changes_only) and deactivated nodes (IN changes_only). + */ + return b_fs::exists(get_change_path()); +} + +/* 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 vtw_def& def, bool is_set) +{ + b_fs::path cpath = mutable_cfg_path; + string com_str = cpath.file_string() + "/"; + if (def.is_value && !def.tag) { + // path includes leaf value. construct the right string. + string val = _unescape_path_name(cpath.filename()); + cpath = cpath.parent_path(); + /* 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.multi) { + val = _escape_path_name(val); + } + com_str = cpath.file_string() + "/value:" + val; + } + com_str = (is_set ? "+ " : "- ") + com_str; + return committed_marker_exists(com_str); +} + +bool +UnionfsCstore::validate_val_impl(vtw_def *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, value); + var_ref_handle = NULL; + return ret; +} + +void +UnionfsCstore::get_edit_level(vector& pcomps) { + b_fs::path opath = mutable_cfg_path; // use a copy + while (opath.has_parent_path()) { + pcomps.insert(pcomps.begin(), pop_path(opath)); + } +} + +string +UnionfsCstore::cfg_path_to_str() { + string cpath = mutable_cfg_path.file_string(); + if (cpath.length() == 0) { + cpath = "/"; + } + return cpath; +} + +string +UnionfsCstore::tmpl_path_to_str() { + // return only the mutable part + string tpath = tmpl_path.file_string(); + tpath.erase(0, tmpl_root.file_string().length()); + if (tpath.length() == 0) { + tpath = "/"; + } + return tpath; +} + + +////// private functions +void +UnionfsCstore::push_path(b_fs::path& old_path, const string& new_comp) +{ + string comp = _escape_path_name(new_comp); + old_path /= comp; +} + +string +UnionfsCstore::pop_path(b_fs::path& path) +{ + string ret = _unescape_path_name(path.filename()); + /* note: contrary to documentation, remove_filename() does not remove + * trailing slash. + */ + path = path.parent_path(); + return ret; +} + +void +UnionfsCstore::get_all_child_dir_names(b_fs::path root, vector& nodes) +{ + if (!b_fs::exists(root) || !b_fs::is_directory(root)) { + // not a valid root. nop. + return; + } + b_fs::directory_iterator di(root); + for (; di != b_fs::directory_iterator(); ++di) { + // must be directory + if (!b_fs::is_directory(di->path())) { + continue; + } + // name cannot start with "." + if (di->path().file_string().substr(0, 1) == ".") { + continue; + } + // found one + nodes.push_back(_unescape_path_name(di->path().filename())); + } +} + +bool +UnionfsCstore::write_file(const string& file, const string& data) +{ + try { + // make sure the path exists + b_fs::path fpath(file); + b_fs::create_directories(fpath.parent_path()); + + // write the file + ofstream fout; + fout.exceptions(ofstream::failbit | ofstream::badbit); + fout.open(file.c_str(), ios_base::out | ios_base::trunc); + fout << data; + fout.close(); + } catch (...) { + return false; + } + return true; +} + +bool +UnionfsCstore::read_whole_file(const b_fs::path& fpath, string& data) +{ + /* must exist, be a regular file, and smaller than limit (we're going + * to read the whole thing). + */ + if (!b_fs::exists(fpath) || !b_fs::is_regular(fpath) + || b_fs::file_size(fpath) > MAX_FILE_READ_SIZE) { + return false; + } + stringbuf sbuf; + ifstream fin(fpath.file_string().c_str()); + fin >> &sbuf; + fin.close(); + /* note: if file contains just a newline => (eof() && fail()) + * so only checking bad() and eof() (we want whole file). + */ + if (fin.bad() || !fin.eof()) { + // read failed + return false; + } + data = sbuf.str(); + return true; +} + +/* return whether specified "commited marker" exists in the + * "committed marker file". + */ +bool +UnionfsCstore::committed_marker_exists(const string& marker) +{ + bool ret = false; + ifstream fin(C_COMMITTED_MARKER_FILE.c_str()); + while (!fin.eof() && !fin.bad() && !fin.fail()) { + string line; + getline(fin, line); + if (line == marker) { + ret = true; + break; + } + } + fin.close(); + return ret; +} + +/* recursively copy source directory to destination. + * will throw exception (from b_fs) if fail. + */ +void +UnionfsCstore::recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst) +{ + string src_str = src.file_string(); + string dst_str = dst.file_string(); + b_fs::create_directory(dst); + + b_fs::recursive_directory_iterator di(src); + for (; di != b_fs::recursive_directory_iterator(); ++di) { + b_fs::path opath = di->path(); + string nname = opath.file_string(); + nname.replace(0, src_str.length(), dst_str); + b_fs::path npath = nname; + if (b_fs::is_directory(opath)) { + b_fs::create_directory(npath); + } else { + b_fs::copy_file(opath, npath); + } + } +} + diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp new file mode 100644 index 0000000..357e307 --- /dev/null +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2010 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 . + */ + +#ifndef _CSTORE_UNIONFS_H_ +#define _CSTORE_UNIONFS_H_ +#include +#include + +#include +#include +#include +#include + +#include + +#include + +extern "C" { +#include +#include +} + +namespace b_fs = boost::filesystem; + +class UnionfsCstore : public Cstore { +public: + UnionfsCstore(bool use_edit_level = false); + UnionfsCstore(const string& session_id, string& env); + ~UnionfsCstore(); + + ////// public virtual functions declared in base class + bool markSessionUnsaved(); + bool unmarkSessionUnsaved(); + bool sessionUnsaved(); + bool sessionChanged(); + bool setupSession(); + bool teardownSession(); + bool inSession(); + +private: + // constants + static const string C_ENV_TMPL_ROOT; + static const string C_ENV_WORK_ROOT; + static const string C_ENV_ACTIVE_ROOT; + static const string C_ENV_CHANGE_ROOT; + static const string C_ENV_TMP_ROOT; + + static const string C_DEF_TMPL_ROOT; + static const string C_DEF_CFG_ROOT; + static const string C_DEF_ACTIVE_ROOT; + static const string C_DEF_CHANGE_PREFIX; + static const string C_DEF_WORK_PREFIX; + static const string C_DEF_TMP_PREFIX; + + static const string C_MARKER_DEF_VALUE; + static const string C_MARKER_DEACTIVATE; + static const string C_MARKER_CHANGED; + static const string C_MARKER_UNSAVED; + static const string C_COMMITTED_MARKER_FILE; + static const string C_COMMENT_FILE; + + static const size_t MAX_FILE_READ_SIZE = 8192; + + // root dirs (constant) + b_fs::path work_root; // working root (union) + b_fs::path active_root; // active root (readonly part of union) + b_fs::path change_root; // change root (r/w part of union) + b_fs::path tmp_root; // temp root + b_fs::path tmpl_root; // template root + + // path buffers + b_fs::path mutable_cfg_path; // mutable part of config path + b_fs::path tmpl_path; // whole template path + map > saved_paths; + // saved mutable part of cfg path and whole template path + + ////// virtual functions defined in base class + // begin path modifiers + void push_tmpl_path(const string& new_comp) { + push_path(tmpl_path, new_comp); + }; + void push_tmpl_path_tag() { + push_tmpl_path(TAG_NAME); + }; + string pop_tmpl_path() { + return pop_path(tmpl_path); + }; + void push_cfg_path(const string& new_comp) { + push_path(mutable_cfg_path, new_comp); + }; + void push_cfg_path_val() { + push_cfg_path(VAL_NAME); + }; + string pop_cfg_path() { + return pop_path(mutable_cfg_path); + }; + void append_cfg_path(const vector& path_comps) { + for (unsigned int i = 0; i < path_comps.size(); i++) { + push_cfg_path(path_comps[i]); + } + }; + void reset_paths() { + tmpl_path = tmpl_root; + mutable_cfg_path = ""; + }; + void save_paths(const void *handle = NULL) { + pair p; + p.first = mutable_cfg_path; + p.second = tmpl_path; + saved_paths[handle] = p; + }; + void restore_paths(const void *handle = NULL) { + map >::iterator it + = saved_paths.find(handle); + if (it == saved_paths.end()) { + exit_internal("restore_paths: handle not found\n"); + } + pair p = saved_paths[handle]; + mutable_cfg_path = p.first; + tmpl_path = p.second; + }; + bool cfg_path_at_root() { + return (!mutable_cfg_path.has_parent_path()); + }; + bool tmpl_path_at_root() { + return (tmpl_path.file_string() == tmpl_root.file_string()); + }; + // end path modifiers + + // these operate on current tmpl path + bool tmpl_node_exists(); + bool tmpl_parse(vtw_def& def); + + // these operate on current work path + bool add_node(); + bool remove_node(); + void get_all_child_node_names_impl(vector& cnodes, bool active_cfg); + void get_all_tmpl_child_node_names(vector& cnodes) { + get_all_child_dir_names(tmpl_path, cnodes); + }; + bool write_value_vec(const vector& vvec, bool active_cfg); + bool rename_child_node(const string& oname, const string& nname); + bool copy_child_node(const string& oname, const string& nname); + bool mark_display_default(); + bool unmark_display_default(); + bool mark_deactivated(); + bool unmark_deactivated(); + bool unmark_deactivated_descendants(); + bool mark_changed(); + bool remove_comment(); + bool set_comment(const string& comment); + bool discard_changes(unsigned long long& num_removed); + + // observers for work path + bool marked_changed(); + bool marked_display_default(); + + // observers for work path or active path + bool cfg_node_exists(bool active_cfg); + bool read_value_vec(vector& vvec, bool active_cfg); + bool marked_deactivated(bool active_cfg); + bool get_comment(string& comment, bool active_cfg); + + // observers during commit operation + bool marked_committed(const vtw_def& def, bool is_set); + + // these operate on both current tmpl and work paths + bool validate_val_impl(vtw_def *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() { + return cfg_path_to_str(); + }; + string get_tmpl_level_path() { + return tmpl_path_to_str(); + }; + void get_edit_level(vector& path_comps); + bool edit_level_at_root() { + return cfg_path_at_root(); + }; + + // for testing/debugging + string cfg_path_to_str(); + string tmpl_path_to_str(); + + ////// private functions + b_fs::path get_work_path() { return (work_root / mutable_cfg_path); }; + b_fs::path get_active_path() { return (active_root / mutable_cfg_path); }; + b_fs::path get_change_path() { return (change_root / mutable_cfg_path); }; + void push_path(b_fs::path& old_path, const string& new_comp); + string pop_path(b_fs::path& path); + void get_all_child_dir_names(b_fs::path root, vector& nodes); + bool write_file(const string& file, const string& data); + bool create_file(const string& file) { + return write_file(file, ""); + }; + bool read_whole_file(const b_fs::path& file, string& data); + bool committed_marker_exists(const string& marker); + void recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst); +}; + +#endif /* _CSTORE_UNIONFS_H_ */ + -- cgit v1.2.3 From a56723cbb6424e1d49289efceeb9c251eed324bb Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Wed, 28 Jul 2010 19:08:43 -0700 Subject: add cstore-specific header file --- Makefile.am | 4 +- src/cli_bin.cpp | 5 +- src/cli_cstore.h | 147 ++++++++++++++++++++++++++++++++++ src/cli_new.c | 20 ++--- src/cli_shell_api.cpp | 5 +- src/cli_val.h | 110 +------------------------ src/cstore/cstore-c.cpp | 11 +-- src/cstore/cstore-c.h | 7 +- src/cstore/cstore-varref.cpp | 8 +- src/cstore/cstore-varref.hpp | 2 +- src/cstore/cstore.cpp | 37 ++++----- src/cstore/cstore.hpp | 8 +- src/cstore/unionfs/cstore-unionfs.cpp | 19 +++-- src/cstore/unionfs/cstore-unionfs.hpp | 13 ++- 14 files changed, 210 insertions(+), 186 deletions(-) create mode 100644 src/cli_cstore.h (limited to 'src/cstore') diff --git a/Makefile.am b/Makefile.am index b38f0fd..7c93743 100644 --- a/Makefile.am +++ b/Makefile.am @@ -37,9 +37,7 @@ LDADD = src/libvyatta-cfg.la LDADD += /usr/lib/libglib-2.0.la vincludedir = $(includedir)/vyatta-cfg -vinclude_HEADERS = src/cli_val.h -vinclude_HEADERS += src/cli_val_engine.h -vinclude_HEADERS += src/cli_path_utils.h +vinclude_HEADERS = src/cli_cstore.h vcincdir = $(vincludedir)/cstore vcinc_HEADERS = src/cstore/cstore-c.h diff --git a/src/cli_bin.cpp b/src/cli_bin.cpp index 420d19c..25a86ce 100644 --- a/src/cli_bin.cpp +++ b/src/cli_bin.cpp @@ -20,10 +20,7 @@ #include #include -extern "C" { -#include -} - +#include #include static int op_idx = -1; diff --git a/src/cli_cstore.h b/src/cli_cstore.h new file mode 100644 index 0000000..c03bddf --- /dev/null +++ b/src/cli_cstore.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2010 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 . + */ + +#ifndef _CLI_VAL_CSTORE_H_ +#define _CLI_VAL_CSTORE_H_ +#ifdef __cplusplus +extern "C" { +#endif + +/* this header file contains all definitions/declarations in the original + * CLI implementation that are needed by the cstore library. + */ + +/* types */ +typedef int boolean; + +typedef enum { + ERROR_TYPE, + INT_TYPE, + IPV4_TYPE, + IPV4NET_TYPE, + IPV6_TYPE, + IPV6NET_TYPE, + MACADDR_TYPE, + DOMAIN_TYPE, /*end of addr types */ + TEXT_TYPE, + BOOL_TYPE, + PRIORITY_TYPE +} vtw_type_e; + +typedef struct { + vtw_type_e val_type; + char *val; + int cnt; /* >0 means multivalue */ + char **vals; /* We might union with val */ + vtw_type_e *val_types; /* used with vals and multitypes */ + boolean free_me; +} valstruct; + +typedef enum { + LIST_OP, /* right is next, left is list elem */ + HELP_OP, /* right is help string, left is elem */ + EXEC_OP, /* left command string, right help string */ + PATTERN_OP, /* left to var, right to pattern */ + OR_OP, + AND_OP, + NOT_OP, + COND_OP, /* aux field specifies cond type (GT, GE, etc.)*/ + VAL_OP, /* for strings used in other nodes */ + VAR_OP, /* string points to var */ + B_QUOTE_OP, /* string points to operand to be executed */ + ASSIGN_OP /* left to var, right to exp */ +} vtw_oper_e; + +typedef struct vtw_node{ + vtw_oper_e vtw_node_oper; + struct vtw_node *vtw_node_left; + struct vtw_node *vtw_node_right; + char *vtw_node_string; + int vtw_node_aux; + vtw_type_e vtw_node_type; + valstruct vtw_node_val; /* we'll union it later */ +} vtw_node; + +typedef struct { + vtw_node *vtw_list_head; + vtw_node *vtw_list_tail; +} vtw_list; + +typedef enum { + delete_act, + create_act, + activate_act, + update_act, + syntax_act, + commit_act, + begin_act, + end_act, + top_act +} vtw_act_type; + +typedef struct { + vtw_type_e def_type; + vtw_type_e def_type2; + char *def_type_help; + char *def_node_help; + char *def_default; + unsigned int def_priority; + char *def_priority_ext; + char *def_enumeration; + char *def_comp_help; + char *def_allowed; + char *def_val_help; + unsigned int def_tag; + unsigned int def_multi; + boolean tag; + boolean multi; + vtw_list actions[top_act]; + int is_value; /* this is used by the config store to indicate whether + * the last path component is a "value". */ +} vtw_def; + +/* extern variables */ +extern void *var_ref_handle; +extern FILE *out_stream; + +/* note that some functions may be used outside the actual CLI operations, + * so output may not have been initialized. nop in such cases. + */ +#define OUTPUT_USER(fmt, args...) do \ + { \ + if (out_stream) { \ + fprintf(out_stream, fmt , ##args); \ + } \ + } while (0); + +/* functions */ +const valstruct *get_syntax_self_in_valstruct(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(vtw_def *def, char *value); +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); + +#ifdef __cplusplus +} +#endif +#endif /* _CLI_VAL_CSTORE_H_ */ + diff --git a/src/cli_new.c b/src/cli_new.c index 9cc9777..a047f36 100644 --- a/src/cli_new.c +++ b/src/cli_new.c @@ -1471,18 +1471,19 @@ static int eval_va(valstruct *res, vtw_node *node) * cstore implementation. * handle is set => we are in cstore operation. */ - clind_val cv; - if (!cstore_get_var_ref(var_ref_handle, pathp, &cv, + vtw_type_e vtype; + char *vptr = NULL; + if (!cstore_get_var_ref(var_ref_handle, pathp, &vtype, &vptr, is_in_delete_action())) { status = -1; } else { /* success */ status = 0; - if(cv.value) { - res->val_type = cv.val_type; + if(vptr) { + res->val_type = vtype; res->val_types = NULL; res->free_me = TRUE; - res->val = cv.value; + res->val = vptr; } } } else { @@ -1648,10 +1649,11 @@ static int expand_string(char *stringp) * cstore implementation. * handle is set => we are in cstore operation. */ - clind_val cv; - if (cstore_get_var_ref(var_ref_handle, scanp, &cv, - is_in_delete_action())) { - cp=cv.value; + vtw_type_e vtype; + char *vptr = NULL; + if (cstore_get_var_ref(var_ref_handle, scanp, &vtype, &vptr, + is_in_delete_action()) && vptr) { + cp = vptr; } } else { /* legacy usage */ diff --git a/src/cli_shell_api.cpp b/src/cli_shell_api.cpp index 0962c80..22a5cad 100644 --- a/src/cli_shell_api.cpp +++ b/src/cli_shell_api.cpp @@ -22,10 +22,7 @@ #include #include -extern "C" { -#include -} - +#include #include static int op_idx = -1; diff --git a/src/cli_val.h b/src/cli_val.h index 41e4461..536d964 100644 --- a/src/cli_val.h +++ b/src/cli_val.h @@ -2,9 +2,10 @@ #define CLI_DEF_H #include +#include + #define BITWISE 0 /* no partial commit */ -#define boolean int #ifndef FALSE #define FALSE 0 #endif @@ -21,19 +22,6 @@ typedef enum { create_mode, update_mode }vtw_cmode; -typedef enum { - ERROR_TYPE, - INT_TYPE, - IPV4_TYPE, - IPV4NET_TYPE, - IPV6_TYPE, - IPV6NET_TYPE, - MACADDR_TYPE, - DOMAIN_TYPE, /*end of addr types */ - TEXT_TYPE, - BOOL_TYPE, - PRIORITY_TYPE -}vtw_type_e; typedef enum { EQ_COND = 1, @@ -47,83 +35,11 @@ typedef enum { }vtw_cond_e; /* IN_COND is like EQ for singular compare, but OR for multivalue right operand */ -typedef enum { - LIST_OP, /* right is next, left is list elem */ - HELP_OP, /* right is help string, left is elem */ - EXEC_OP, /* left command string, right help string */ - PATTERN_OP, /* left to var, right to pattern */ - OR_OP, - AND_OP, - NOT_OP, - COND_OP, /* aux field specifies cond type (GT, GE, etc.)*/ - VAL_OP, /* for strings used in other nodes */ - VAR_OP, /* string points to var */ - B_QUOTE_OP, /* string points to operand to be executed */ - ASSIGN_OP /* left to var, right to exp */ -}vtw_oper_e; - -typedef struct { - vtw_type_e val_type; - char *val; - int cnt; /* >0 means multivalue */ - char **vals; /* We might union with val */ - vtw_type_e *val_types; /* used with vals and multitypes */ - boolean free_me; -}valstruct; - -typedef struct vtw_node{ - vtw_oper_e vtw_node_oper; - struct vtw_node *vtw_node_left; - struct vtw_node *vtw_node_right; - char *vtw_node_string; - int vtw_node_aux; - vtw_type_e vtw_node_type; - valstruct vtw_node_val; /* we'll union it later */ -}vtw_node; - -typedef struct { - vtw_node *vtw_list_head; - vtw_node *vtw_list_tail; -}vtw_list; - typedef struct { int t_lev; int m_lev; }vtw_mark; -typedef enum { - delete_act, - create_act, - activate_act, - update_act, - syntax_act, - commit_act, - begin_act, - end_act, - top_act -}vtw_act_type; - -typedef struct { - vtw_type_e def_type; - vtw_type_e def_type2; - char *def_type_help; - char *def_node_help; - char *def_default; - unsigned int def_priority; - char *def_priority_ext; - char *def_enumeration; - char *def_comp_help; - char *def_allowed; - char *def_val_help; - unsigned int def_tag; - unsigned int def_multi; - boolean tag; - boolean multi; - vtw_list actions[top_act]; - int is_value; /* this is used by the config store to indicate whether - * the last path component is a "value". */ -}vtw_def; - typedef struct { const char *f_segp; int f_seglen; @@ -162,10 +78,6 @@ extern vtw_node *make_str_node(char *str); extern vtw_node *make_var_node(char *str); extern vtw_node *make_str_node0(char *str, vtw_oper_e op); extern void append(vtw_list *l, vtw_node *n, int aux); -const valstruct *get_syntax_self_in_valstruct(vtw_node *vnode); -int get_shell_command_output(const char *cmd, char *buf, - unsigned int buf_size); -extern int parse_def(vtw_def *defp, const char *path, boolean type_only); extern int yy_cli_val_lex(void); extern void cli_val_start(char *s); @@ -178,7 +90,6 @@ extern void free_def(vtw_def *defp); extern void free_sorted(vtw_sorted *sortp); extern vtw_path m_path, t_path; -extern void *var_ref_handle; /************************************************* GLOBAL FUNCTIONS @@ -193,14 +104,10 @@ extern boolean val_cmp(const valstruct *left, const valstruct *right, vtw_cond_e cond); extern void out_of_memory(void) __attribute__((noreturn)); extern void subtract_values(char **lhs, const char *rhs); -extern boolean validate_value(vtw_def *def, - char *value); extern void internal_error(int line, const char *file) __attribute__((noreturn)); extern void done(void); extern void del_value(vtw_def *defp, char *cp); -extern void bye(const char *msg, ...) - __attribute__((format(printf, 1, 2), noreturn)); extern void print_msg(const char *msg, ...) __attribute__((format(printf, 1, 2))); extern void switch_path(first_seg *seg); @@ -209,7 +116,6 @@ extern void free_val(valstruct *val); extern void touch(void); extern int mkdir_p(const char *path); -extern const char *type_to_name(vtw_type_e type); extern boolean execute_list(vtw_node *cur, vtw_def *def, const char **outbuf); extern void touch_dir(const char *dp); extern void touch_file(const char *name); @@ -237,20 +143,8 @@ extern int get_config_lock(void); #define LOGFILE_STDERR "/tmp/cfg-stderr.log" extern int out_fd; -extern FILE *out_stream; extern FILE *err_stream; -extern int initialize_output(const char *op); -/* note that some functions may be used outside the actual CLI operations, - * so output may not have been initialized. nop in such cases. - */ -#define OUTPUT_USER(fmt, args...) do \ - { \ - if (out_stream) { \ - fprintf(out_stream, fmt , ##args); \ - } \ - } while (0); - /* debug hooks? */ #define my_malloc(size, name) malloc(size) #define my_realloc(ptr, size, name) realloc(ptr, size) diff --git a/src/cstore/cstore-c.cpp b/src/cstore/cstore-c.cpp index 3215707..0e25b3c 100644 --- a/src/cstore/cstore-c.cpp +++ b/src/cstore/cstore-c.cpp @@ -18,8 +18,8 @@ #include #include -#include "cstore-c.h" -#include "cstore/unionfs/cstore-unionfs.hpp" +#include +#include void * cstore_init(void) @@ -80,12 +80,13 @@ cstore_cfg_path_exists(void *handle, const char *path_comps[], int num_comps) } int -cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval, - int from_active) +cstore_get_var_ref(void *handle, const char *ref_str, vtw_type_e *type, + char **val, int from_active) { if (handle) { Cstore *cs = (Cstore *) handle; - return (cs->getVarRef(ref_str, *cval, from_active) ? 1 : 0); + *val = cs->getVarRef(ref_str, *type, from_active); + return (*val ? 1 : 0); } return 0; } diff --git a/src/cstore/cstore-c.h b/src/cstore/cstore-c.h index e664f95..c2c5fa0 100644 --- a/src/cstore/cstore-c.h +++ b/src/cstore/cstore-c.h @@ -20,8 +20,7 @@ extern "C" { #endif -#include -#include +#include void *cstore_init(void); void cstore_free(void *handle); @@ -39,8 +38,8 @@ int cstore_cfg_path_deactivated(void *handle, const char *path_comps[], * during cstore operations since they operate on "current" paths constructed * by the operations. */ -int cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval, - int from_active); +int cstore_get_var_ref(void *handle, const char *ref_str, vtw_type_e *type, + char **val, int from_active); int cstore_set_var_ref(void *handle, const char *ref_str, const char *value, int to_active); diff --git a/src/cstore/cstore-varref.cpp b/src/cstore/cstore-varref.cpp index 6d71307..46950fa 100644 --- a/src/cstore/cstore-varref.cpp +++ b/src/cstore/cstore-varref.cpp @@ -19,12 +19,8 @@ #include #include -#include "cstore-varref.hpp" - -extern "C" { -#include "cli_val.h" -#include "cli_objects.h" -} +#include +#include using namespace std; diff --git a/src/cstore/cstore-varref.hpp b/src/cstore/cstore-varref.hpp index 1fc1d52..7ab523f 100644 --- a/src/cstore/cstore-varref.hpp +++ b/src/cstore/cstore-varref.hpp @@ -20,7 +20,7 @@ #include #include -#include "cstore.hpp" +#include using namespace std; diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp index 31c896a..e97ea2c 100644 --- a/src/cstore/cstore.cpp +++ b/src/cstore/cstore.cpp @@ -25,12 +25,9 @@ #include #include -extern "C" { -#include "cli_val.h" -} - -#include "cstore.hpp" -#include "cstore-varref.hpp" +#include +#include +#include ////// constants @@ -57,8 +54,9 @@ const string Cstore::C_ENV_SHAPI_COMP_HELP = "_cli_shell_api_comp_help"; const string Cstore::C_ENV_SHAPI_HELP_ITEMS = "_cli_shell_api_hitems"; const string Cstore::C_ENV_SHAPI_HELP_STRS = "_cli_shell_api_hstrs"; -//// dirs +//// dirs/files const string Cstore::C_ENUM_SCRIPT_DIR = "/opt/vyatta/share/enumeration"; +const string Cstore::C_LOGFILE_STDOUT = "/tmp/cfg-stdout.log"; ////// constructors/destructors /* this constructor just returns the generic environment string, @@ -1691,25 +1689,24 @@ Cstore::cfgPathGetEffectiveValues(const vector& path_comps, /* get the value string that corresponds to specified variable ref string. * ref_str: var ref string (e.g., "./cost/@"). - * cval: (output) contains the resulting string. + * type: (output) the node type. * from_active: if true, value string should come from "active config". * otherwise from "working config". - * return true if successful. otherwise return false. + * return a pointer to the value string if successful (caller must free). + * otherwise return NULL. */ -bool -Cstore::getVarRef(const string& ref_str, clind_val& cval, bool from_active) +char * +Cstore::getVarRef(const string& ref_str, vtw_type_e& type, bool from_active) { - bool ret = true; + char *ret = NULL; SAVE_PATHS; VarRef vref(this, ref_str, from_active); string val; - vtw_type_e type; - if (!vref.getValue(val, type)) { - ret = false; - } else { - cval.val_type = type; + vtw_type_e t; + if (vref.getValue(val, t)) { + type = t; // follow original implementation. caller is supposed to free this. - cval.value = strdup(val.c_str()); + ret = strdup(val.c_str()); } RESTORE_PATHS; return ret; @@ -1855,8 +1852,8 @@ Cstore::output_internal(const char *fmt, ...) int fdout = -1; FILE *fout = NULL; do { - // XXX for now use the constant from cli_val.h - if ((fdout = open(LOGFILE_STDOUT, O_WRONLY | O_CREAT, 0660)) == -1) { + if ((fdout = open(C_LOGFILE_STDOUT.c_str(), + O_WRONLY | O_CREAT, 0660)) == -1) { break; } if (lseek(fdout, 0, SEEK_END) == ((off_t) -1)) { diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp index f6a4215..1d4ffe2 100644 --- a/src/cstore/cstore.hpp +++ b/src/cstore/cstore.hpp @@ -20,10 +20,7 @@ #include #include -extern "C" { -#include -#include -} +#include #define exit_internal(fmt, args...) do \ { \ @@ -65,6 +62,7 @@ public: static const string C_ENV_SHAPI_HELP_STRS; static const string C_ENUM_SCRIPT_DIR; + static const string C_LOGFILE_STDOUT; static const size_t MAX_CMD_OUTPUT_SIZE = 4096; @@ -245,7 +243,7 @@ public: * the limitations of the original CLI library implementation and MUST NOT * be used by anyone other than the original CLI library. */ - bool getVarRef(const string& ref_str, clind_val& cval, bool from_active); + char *getVarRef(const string& ref_str, vtw_type_e& type, bool from_active); bool setVarRef(const string& ref_str, const string& value, bool to_active); protected: diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp index 977a597..6e6e5de 100644 --- a/src/cstore/unionfs/cstore-unionfs.cpp +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -24,12 +24,8 @@ #include #include -extern "C" { -#include "cli_val.h" -#include "cli_objects.h" -} - -#include "cstore-unionfs.hpp" +#include +#include ////// constants @@ -61,6 +57,9 @@ 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_COMMENT_FILE = ".comment"; +const string UnionfsCstore::C_TAG_NAME = "node.tag"; +const string UnionfsCstore::C_VAL_NAME = "node.val"; +const string UnionfsCstore::C_DEF_NAME = "node.def"; ////// static @@ -414,9 +413,9 @@ UnionfsCstore::tmpl_node_exists() bool UnionfsCstore::tmpl_parse(vtw_def& def) { - push_tmpl_path(DEF_NAME); + push_tmpl_path(C_DEF_NAME); bool ret = (b_fs::exists(tmpl_path) && b_fs::is_regular(tmpl_path) - && parse_def(&def, tmpl_path.file_string().c_str(), FALSE) == 0); + && parse_def(&def, tmpl_path.file_string().c_str(), 0) == 0); pop_tmpl_path(); return ret; } @@ -490,8 +489,8 @@ UnionfsCstore::get_all_child_node_names_impl(vector& cnodes, * node.val * def */ - if (b_fs::exists(p / VAL_NAME) && b_fs::is_regular(p / VAL_NAME)) { - cnodes.push_back(VAL_NAME); + if (b_fs::exists(p / C_VAL_NAME) && b_fs::is_regular(p / C_VAL_NAME)) { + cnodes.push_back(C_VAL_NAME); } if (b_fs::exists(p / C_MARKER_DEF_VALUE) && b_fs::is_regular(p / C_MARKER_DEF_VALUE)) { diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp index 357e307..dd44d9a 100644 --- a/src/cstore/unionfs/cstore-unionfs.hpp +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -26,13 +26,9 @@ #include +#include #include -extern "C" { -#include -#include -} - namespace b_fs = boost::filesystem; class UnionfsCstore : public Cstore { @@ -71,6 +67,9 @@ private: static const string C_MARKER_UNSAVED; static const string C_COMMITTED_MARKER_FILE; static const string C_COMMENT_FILE; + static const string C_TAG_NAME; + static const string C_VAL_NAME; + static const string C_DEF_NAME; static const size_t MAX_FILE_READ_SIZE = 8192; @@ -93,7 +92,7 @@ private: push_path(tmpl_path, new_comp); }; void push_tmpl_path_tag() { - push_tmpl_path(TAG_NAME); + push_tmpl_path(C_TAG_NAME); }; string pop_tmpl_path() { return pop_path(tmpl_path); @@ -102,7 +101,7 @@ private: push_path(mutable_cfg_path, new_comp); }; void push_cfg_path_val() { - push_cfg_path(VAL_NAME); + push_cfg_path(C_VAL_NAME); }; string pop_cfg_path() { return pop_path(mutable_cfg_path); -- cgit v1.2.3 From dc4bd2c05375cece9d1c1281cbebbef40a09c4e4 Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Fri, 30 Jul 2010 09:53:00 -0700 Subject: add default status observers --- lib/Vyatta/Config.pm | 23 +++++++++++++++++++++-- lib/Vyatta/ConfigLoad.pm | 4 ++-- perl_dmod/Cstore/Cstore.xs | 10 ++++++++++ src/cstore/cstore.cpp | 16 +++++++++++++++- src/cstore/cstore.hpp | 4 +++- src/cstore/unionfs/cstore-unionfs.cpp | 5 +++-- src/cstore/unionfs/cstore-unionfs.hpp | 2 +- 7 files changed, 55 insertions(+), 9 deletions(-) (limited to 'src/cstore') diff --git a/lib/Vyatta/Config.pm b/lib/Vyatta/Config.pm index 8067e05..371fe32 100755 --- a/lib/Vyatta/Config.pm +++ b/lib/Vyatta/Config.pm @@ -53,8 +53,9 @@ sub get_path_comps { } ############################################################ -# low-level API functions that have been converted to use -# the cstore library. +# low-level API functions that use the cstore library directly. +# they are either new functions or old ones that have been +# converted to use cstore. ############################################################ ###### @@ -94,6 +95,24 @@ sub existsOrig { return; # note: this return is needed. } +## isDefault("path to node") +# Returns true if specified node is "default" in working config. +sub isDefault { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathDefault($self->get_path_comps($path), undef)); + return; # note: this return is needed. +} + +## isDefaultOrig("path to node") +# Returns true if specified node is "default" in active config. +sub isDefaultOrig { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathDefault($self->get_path_comps($path), 1)); + return; # note: this return is needed. +} + ## listNodes("level") # return array of all child nodes at "level" in working config. sub listNodes { diff --git a/lib/Vyatta/ConfigLoad.pm b/lib/Vyatta/ConfigLoad.pm index d3d7dbb..55ba76b 100755 --- a/lib/Vyatta/ConfigLoad.pm +++ b/lib/Vyatta/ConfigLoad.pm @@ -401,8 +401,8 @@ sub getConfigDiff { $file; } @{${$del}[0]}; - my ($is_multi, $is_text, $default) - = $active_cfg->parseTmpl(join ' ', @comps); + $active_cfg->setLevel(join ' ', @comps); + my ($is_multi, $is_text, $default) = $active_cfg->parseTmpl(); if (!defined($default)) { push @new_delete_list, $del; } diff --git a/perl_dmod/Cstore/Cstore.xs b/perl_dmod/Cstore/Cstore.xs index 5dd105e..f72a124 100644 --- a/perl_dmod/Cstore/Cstore.xs +++ b/perl_dmod/Cstore/Cstore.xs @@ -54,6 +54,16 @@ OUTPUT: RETVAL +bool +Cstore::cfgPathDefault(STRVEC *vref, bool active_cfg) +PREINIT: + vector arg_strvec; +CODE: + RETVAL = THIS->cfgPathDefault(arg_strvec, active_cfg); +OUTPUT: + RETVAL + + STRVEC * Cstore::cfgPathGetChildNodes(STRVEC *vref, bool active_cfg) PREINIT: diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp index e97ea2c..c829f6e 100644 --- a/src/cstore/cstore.cpp +++ b/src/cstore/cstore.cpp @@ -853,7 +853,7 @@ Cstore::setCfgPath(const vector& path_comps) append_cfg_path(path_comps); pop_cfg_path(); // only do it if it's previously marked default - if (marked_display_default()) { + if (marked_display_default(false)) { ret = unmark_display_default(); /* XXX work around current commit's unionfs implementation problem. @@ -1448,6 +1448,20 @@ Cstore::cfgPathGetComment(const vector& path_comps, string& comment, return ret; } +/* return whether specified path is "default". if a node is "default", it + * is currently not shown by the "show" command unless "-all" is specified. + * active_cfg: whether to observe active config. + */ +bool +Cstore::cfgPathDefault(const vector& path_comps, bool active_cfg) +{ + SAVE_PATHS; + append_cfg_path(path_comps); + bool ret = marked_display_default(active_cfg); + RESTORE_PATHS; + return ret; +} + /* the following functions are observers of the "effective" config. * they can be used * (1) outside a config session (e.g., op mode, daemons, callbacks, etc.). diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp index 1d4ffe2..1d8a295 100644 --- a/src/cstore/cstore.hpp +++ b/src/cstore/cstore.hpp @@ -172,6 +172,8 @@ public: vector& values, bool active_cfg = false); bool cfgPathGetComment(const vector& path_comps, string& comment, bool active_cfg = false); + bool cfgPathDefault(const vector& path_comps, + bool active_cfg = false); /* observers for working AND active configs (at the same time). * MUST ONLY be used during config session. */ @@ -304,13 +306,13 @@ private: // observers for current work path virtual bool marked_changed() = 0; - virtual bool marked_display_default() = 0; // observers for current work path or active path virtual bool read_value_vec(vector& vvec, bool active_cfg) = 0; virtual bool cfg_node_exists(bool active_cfg) = 0; virtual bool marked_deactivated(bool active_cfg) = 0; 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 vtw_def& def, bool is_set) = 0; diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp index 6e6e5de..3e94778 100644 --- a/src/cstore/unionfs/cstore-unionfs.cpp +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -662,9 +662,10 @@ UnionfsCstore::unmark_display_default() } bool -UnionfsCstore::marked_display_default() +UnionfsCstore::marked_display_default(bool active_cfg) { - b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; + b_fs::path marker = (active_cfg ? get_active_path() : get_work_path()) + / C_MARKER_DEF_VALUE; return b_fs::exists(marker); } diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp index dd44d9a..8bec974 100644 --- a/src/cstore/unionfs/cstore-unionfs.hpp +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -165,13 +165,13 @@ private: // observers for work path bool marked_changed(); - bool marked_display_default(); // observers for work path or active path bool cfg_node_exists(bool active_cfg); bool read_value_vec(vector& vvec, bool active_cfg); bool marked_deactivated(bool active_cfg); bool get_comment(string& comment, bool active_cfg); + bool marked_display_default(bool active_cfg); // observers during commit operation bool marked_committed(const vtw_def& def, bool is_set); -- cgit v1.2.3 From 04d940d86e997cb50daf9964893ada97b8dd0fda Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Fri, 30 Jul 2010 16:22:34 -0700 Subject: make destructors virtual --- src/cstore/cstore.hpp | 2 +- src/cstore/unionfs/cstore-unionfs.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/cstore') diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp index 1d8a295..f34a8af 100644 --- a/src/cstore/cstore.hpp +++ b/src/cstore/cstore.hpp @@ -40,7 +40,7 @@ class Cstore { public: Cstore() {}; Cstore(string& env); - ~Cstore() {}; + virtual ~Cstore() {}; // constants static const string C_NODE_STATUS_DELETED; diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp index 8bec974..2da148e 100644 --- a/src/cstore/unionfs/cstore-unionfs.hpp +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -35,7 +35,7 @@ class UnionfsCstore : public Cstore { public: UnionfsCstore(bool use_edit_level = false); UnionfsCstore(const string& session_id, string& env); - ~UnionfsCstore(); + virtual ~UnionfsCstore(); ////// public virtual functions declared in base class bool markSessionUnsaved(); -- cgit v1.2.3 From f7982ea06e89e816a61218e7fc6229f281cd57bc Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Sat, 31 Jul 2010 14:43:50 -0700 Subject: handle potential exceptions from filesystem operations. --- src/cstore/unionfs/cstore-unionfs.cpp | 191 ++++++++++++++++++---------------- src/cstore/unionfs/cstore-unionfs.hpp | 11 ++ 2 files changed, 114 insertions(+), 88 deletions(-) (limited to 'src/cstore') diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp index 3e94778..f2bcf46 100644 --- a/src/cstore/unionfs/cstore-unionfs.cpp +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -261,7 +261,7 @@ bool UnionfsCstore::markSessionUnsaved() { b_fs::path marker = work_root / C_MARKER_UNSAVED; - if (b_fs::exists(marker)) { + if (b_fs_exists(marker)) { // already marked. treat as success. return true; } @@ -277,7 +277,7 @@ bool UnionfsCstore::unmarkSessionUnsaved() { b_fs::path marker = work_root / C_MARKER_UNSAVED; - if (!b_fs::exists(marker)) { + if (!b_fs_exists(marker)) { // not marked. treat as success. return true; } @@ -295,14 +295,14 @@ bool UnionfsCstore::sessionUnsaved() { b_fs::path marker = work_root / C_MARKER_UNSAVED; - return b_fs::exists(marker); + return b_fs_exists(marker); } bool UnionfsCstore::sessionChanged() { b_fs::path marker = work_root / C_MARKER_CHANGED; - return b_fs::exists(marker); + return b_fs_exists(marker); } /* set up the session associated with this object. @@ -312,13 +312,13 @@ UnionfsCstore::sessionChanged() bool UnionfsCstore::setupSession() { - if (!b_fs::exists(work_root)) { + if (!b_fs_exists(work_root)) { // session doesn't exist. create dirs. try { b_fs::create_directories(work_root); b_fs::create_directories(change_root); b_fs::create_directories(tmp_root); - if (!b_fs::exists(active_root)) { + if (!b_fs_exists(active_root)) { // this should only be needed on boot b_fs::create_directories(active_root); } @@ -336,7 +336,7 @@ UnionfsCstore::setupSession() strerror(errno), work_root.file_string().c_str()); return false; } - } else if (!b_fs::is_directory(work_root)) { + } else if (!b_fs_is_directory(work_root)) { output_internal("setup session not dir [%s]\n", work_root.file_string().c_str()); return false; @@ -354,7 +354,7 @@ UnionfsCstore::teardownSession() // check if session exists string wstr = work_root.file_string(); if (wstr.empty() || wstr.find(C_DEF_WORK_PREFIX) != 0 - || !b_fs::exists(work_root) || !b_fs::is_directory(work_root)) { + || !b_fs_exists(work_root) || !b_fs_is_directory(work_root)) { // no session output_internal("teardown invalid session [%s]\n", wstr.c_str()); return false; @@ -392,7 +392,7 @@ UnionfsCstore::inSession() { string wstr = work_root.file_string(); return (!wstr.empty() && wstr.find(C_DEF_WORK_PREFIX) == 0 - && b_fs::exists(work_root) && b_fs::is_directory(work_root)); + && b_fs_exists(work_root) && b_fs_is_directory(work_root)); } @@ -403,7 +403,7 @@ UnionfsCstore::inSession() bool UnionfsCstore::tmpl_node_exists() { - return (b_fs::exists(tmpl_path) && b_fs::is_directory(tmpl_path)); + return (b_fs_exists(tmpl_path) && b_fs_is_directory(tmpl_path)); } /* parse template at current tmpl_path. @@ -414,7 +414,7 @@ bool UnionfsCstore::tmpl_parse(vtw_def& def) { push_tmpl_path(C_DEF_NAME); - bool ret = (b_fs::exists(tmpl_path) && b_fs::is_regular(tmpl_path) + bool ret = (b_fs_exists(tmpl_path) && b_fs_is_regular(tmpl_path) && parse_def(&def, tmpl_path.file_string().c_str(), 0) == 0); pop_tmpl_path(); return ret; @@ -424,7 +424,7 @@ bool UnionfsCstore::cfg_node_exists(bool active_cfg) { b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); - return (b_fs::exists(p) && b_fs::is_directory(p)); + return (b_fs_exists(p) && b_fs_is_directory(p)); } bool @@ -449,7 +449,7 @@ UnionfsCstore::add_node() bool UnionfsCstore::remove_node() { - if (!b_fs::exists(get_work_path()) || !b_fs::is_directory(get_work_path())) { + if (!b_fs_exists(get_work_path()) || !b_fs_is_directory(get_work_path())) { output_internal("remove non-existent node [%s]\n", get_work_path().file_string().c_str()); return false; @@ -489,11 +489,11 @@ UnionfsCstore::get_all_child_node_names_impl(vector& cnodes, * node.val * def */ - if (b_fs::exists(p / C_VAL_NAME) && b_fs::is_regular(p / C_VAL_NAME)) { + if (b_fs_exists(p / C_VAL_NAME) && b_fs_is_regular(p / C_VAL_NAME)) { cnodes.push_back(C_VAL_NAME); } - if (b_fs::exists(p / C_MARKER_DEF_VALUE) - && b_fs::is_regular(p / C_MARKER_DEF_VALUE)) { + if (b_fs_exists(p / C_MARKER_DEF_VALUE) + && b_fs_is_regular(p / C_MARKER_DEF_VALUE)) { cnodes.push_back(C_MARKER_DEF_VALUE); } } @@ -543,7 +543,7 @@ UnionfsCstore::write_value_vec(const vector& vvec, bool active_cfg) bool ret = false; b_fs::path wp = (active_cfg ? get_active_path() : get_work_path()); do { - if (b_fs::exists(wp) && !b_fs::is_regular(wp)) { + if (b_fs_exists(wp) && !b_fs_is_regular(wp)) { // not a file break; } @@ -575,8 +575,8 @@ UnionfsCstore::rename_child_node(const string& oname, const string& nname) { b_fs::path opath = get_work_path() / oname; b_fs::path npath = get_work_path() / nname; - if (!b_fs::exists(opath) || !b_fs::is_directory(opath) - || b_fs::exists(npath)) { + if (!b_fs_exists(opath) || !b_fs_is_directory(opath) + || b_fs_exists(npath)) { output_internal("cannot rename node [%s,%s,%s]\n", get_work_path().file_string().c_str(), oname.c_str(), nname.c_str()); @@ -609,8 +609,8 @@ UnionfsCstore::copy_child_node(const string& oname, const string& nname) { b_fs::path opath = get_work_path() / oname; b_fs::path npath = get_work_path() / nname; - if (!b_fs::exists(opath) || !b_fs::is_directory(opath) - || b_fs::exists(npath)) { + if (!b_fs_exists(opath) || !b_fs_is_directory(opath) + || b_fs_exists(npath)) { output_internal("cannot copy node [%s,%s,%s]\n", get_work_path().file_string().c_str(), oname.c_str(), nname.c_str()); @@ -631,7 +631,7 @@ bool UnionfsCstore::mark_display_default() { b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; - if (b_fs::exists(marker)) { + if (b_fs_exists(marker)) { // already marked. treat as success. return true; } @@ -647,7 +647,7 @@ bool UnionfsCstore::unmark_display_default() { b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE; - if (!b_fs::exists(marker)) { + if (!b_fs_exists(marker)) { // not marked. treat as success. return true; } @@ -666,7 +666,7 @@ UnionfsCstore::marked_display_default(bool active_cfg) { b_fs::path marker = (active_cfg ? get_active_path() : get_work_path()) / C_MARKER_DEF_VALUE; - return b_fs::exists(marker); + return b_fs_exists(marker); } bool @@ -674,14 +674,14 @@ UnionfsCstore::marked_deactivated(bool active_cfg) { b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); b_fs::path marker = p / C_MARKER_DEACTIVATE; - return b_fs::exists(marker); + return b_fs_exists(marker); } bool UnionfsCstore::mark_deactivated() { b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE; - if (b_fs::exists(marker)) { + if (b_fs_exists(marker)) { // already marked. treat as success. return true; } @@ -697,7 +697,7 @@ bool UnionfsCstore::unmark_deactivated() { b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE; - if (!b_fs::exists(marker)) { + if (!b_fs_exists(marker)) { // not deactivated. treat as success. return true; } @@ -717,25 +717,25 @@ UnionfsCstore::unmark_deactivated_descendants() bool ret = false; do { // sanity check - if (!b_fs::is_directory(get_work_path())) { + if (!b_fs_is_directory(get_work_path())) { break; } - vector markers; - b_fs::recursive_directory_iterator di(get_work_path()); - for (; di != b_fs::recursive_directory_iterator(); ++di) { - if (!b_fs::is_regular(di->path()) - || di->path().filename() != C_MARKER_DEACTIVATE) { - // not marker - continue; - } - if (di->path().parent_path() == get_work_path()) { - // don't unmark the node itself - continue; - } - markers.push_back(di->path()); - } try { + vector markers; + b_fs::recursive_directory_iterator di(get_work_path()); + for (; di != b_fs::recursive_directory_iterator(); ++di) { + if (!b_fs_is_regular(di->path()) + || di->path().filename() != C_MARKER_DEACTIVATE) { + // not marker + continue; + } + if (di->path().parent_path() == get_work_path()) { + // don't unmark the node itself + continue; + } + markers.push_back(di->path()); + } for (unsigned int i = 0; i < markers.size(); i++) { b_fs::remove(markers[i]); } @@ -760,7 +760,7 @@ UnionfsCstore::mark_changed() * has changed. */ b_fs::path marker = get_work_path() / C_MARKER_CHANGED; - if (b_fs::exists(marker)) { + if (b_fs_exists(marker)) { // already marked. treat as success. return true; } @@ -789,7 +789,7 @@ bool UnionfsCstore::remove_comment() { b_fs::path cfile = get_work_path() / C_COMMENT_FILE; - if (!b_fs::exists(cfile)) { + if (!b_fs_exists(cfile)) { return false; } try { @@ -817,18 +817,18 @@ UnionfsCstore::discard_changes(unsigned long long& num_removed) // unionfs-specific implementation vector files; vector directories; - // iterate through all entries in change root - b_fs::directory_iterator di(change_root); - for (; di != b_fs::directory_iterator(); ++di) { - if (b_fs::is_directory(di->path())) { - directories.push_back(di->path()); - } else { - files.push_back(di->path()); + try { + // iterate through all entries in change root + b_fs::directory_iterator di(change_root); + for (; di != b_fs::directory_iterator(); ++di) { + if (b_fs_is_directory(di->path())) { + directories.push_back(di->path()); + } else { + files.push_back(di->path()); + } } - } - // remove and count - try { + // remove and count num_removed = 0; for (unsigned int i = 0; i < files.size(); i++) { b_fs::remove(files[i]); @@ -868,7 +868,7 @@ UnionfsCstore::marked_changed() * result will be different between deleted nodes (NOT IN * changes_only) and deactivated nodes (IN changes_only). */ - return b_fs::exists(get_change_path()); + return b_fs_exists(get_change_path()); } /* XXX currently "committed marking" is done inside commit. @@ -968,22 +968,26 @@ UnionfsCstore::pop_path(b_fs::path& path) void UnionfsCstore::get_all_child_dir_names(b_fs::path root, vector& nodes) { - if (!b_fs::exists(root) || !b_fs::is_directory(root)) { + if (!b_fs_exists(root) || !b_fs_is_directory(root)) { // not a valid root. nop. return; } - b_fs::directory_iterator di(root); - for (; di != b_fs::directory_iterator(); ++di) { - // must be directory - if (!b_fs::is_directory(di->path())) { - continue; - } - // name cannot start with "." - if (di->path().file_string().substr(0, 1) == ".") { - continue; + try { + b_fs::directory_iterator di(root); + for (; di != b_fs::directory_iterator(); ++di) { + // must be directory + if (!b_fs_is_directory(di->path())) { + continue; + } + // name cannot start with "." + if (di->path().file_string().substr(0, 1) == ".") { + continue; + } + // found one + nodes.push_back(_unescape_path_name(di->path().filename())); } - // found one - nodes.push_back(_unescape_path_name(di->path().filename())); + } catch (...) { + return; } } @@ -1013,22 +1017,29 @@ UnionfsCstore::read_whole_file(const b_fs::path& fpath, string& data) /* must exist, be a regular file, and smaller than limit (we're going * to read the whole thing). */ - if (!b_fs::exists(fpath) || !b_fs::is_regular(fpath) - || b_fs::file_size(fpath) > MAX_FILE_READ_SIZE) { + if (!b_fs_exists(fpath) || !b_fs_is_regular(fpath)) { return false; } - stringbuf sbuf; - ifstream fin(fpath.file_string().c_str()); - fin >> &sbuf; - fin.close(); - /* note: if file contains just a newline => (eof() && fail()) - * so only checking bad() and eof() (we want whole file). - */ - if (fin.bad() || !fin.eof()) { - // read failed + try { + if (b_fs::file_size(fpath) > MAX_FILE_READ_SIZE) { + return false; + } + + stringbuf sbuf; + ifstream fin(fpath.file_string().c_str()); + fin >> &sbuf; + fin.close(); + /* note: if file contains just a newline => (eof() && fail()) + * so only checking bad() and eof() (we want whole file). + */ + if (fin.bad() || !fin.eof()) { + // read failed + return false; + } + data = sbuf.str(); + } catch (...) { return false; } - data = sbuf.str(); return true; } @@ -1039,16 +1050,20 @@ bool UnionfsCstore::committed_marker_exists(const string& marker) { bool ret = false; - ifstream fin(C_COMMITTED_MARKER_FILE.c_str()); - while (!fin.eof() && !fin.bad() && !fin.fail()) { - string line; - getline(fin, line); - if (line == marker) { - ret = true; - break; + try { + ifstream fin(C_COMMITTED_MARKER_FILE.c_str()); + while (!fin.eof() && !fin.bad() && !fin.fail()) { + string line; + getline(fin, line); + if (line == marker) { + ret = true; + break; + } } + fin.close(); + } catch (...) { + ret = false; } - fin.close(); return ret; } @@ -1068,7 +1083,7 @@ UnionfsCstore::recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst) string nname = opath.file_string(); nname.replace(0, src_str.length(), dst_str); b_fs::path npath = nname; - if (b_fs::is_directory(opath)) { + if (b_fs_is_directory(opath)) { b_fs::create_directory(npath); } else { b_fs::copy_file(opath, npath); diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp index 2da148e..b0e7201 100644 --- a/src/cstore/unionfs/cstore-unionfs.hpp +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -210,6 +210,17 @@ private: bool read_whole_file(const b_fs::path& file, string& data); bool committed_marker_exists(const string& marker); void recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst); + + // boost fs operations wrappers + bool b_fs_exists(const b_fs::path& path) { + try { return b_fs::exists(path); } catch (...) { return false; } + }; + bool b_fs_is_directory(const b_fs::path& path) { + try { return b_fs::is_directory(path); } catch (...) { return false; } + }; + bool b_fs_is_regular(const b_fs::path& path) { + try { return b_fs::is_regular(path); } catch (...) { return false; } + }; }; #endif /* _CSTORE_UNIONFS_H_ */ -- cgit v1.2.3 From 30bf89f4e1c3778ca460fe661c4b83ceb142a1cb Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Sat, 31 Jul 2010 15:03:50 -0700 Subject: make edit do implicit set * this preserves the behavior of the original implementation and allows edit on nonexistent nodes. --- src/cstore/cstore.cpp | 252 +++++++++++++++++++++++++++----------------------- src/cstore/cstore.hpp | 1 + 2 files changed, 138 insertions(+), 115 deletions(-) (limited to 'src/cstore') diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp index c829f6e..28c9d63 100644 --- a/src/cstore/cstore.cpp +++ b/src/cstore/cstore.cpp @@ -390,10 +390,6 @@ Cstore::getEditEnv(const vector& path_comps, string& env) output_user("%s\n", terr.c_str()); return false; } - if (!cfg_path_exists(path_comps, false, true)) { - output_user("The specified config path does not exist\n"); - return false; - } /* "edit" is only allowed when path ends at a * (1) "tag value" * OR @@ -406,6 +402,19 @@ Cstore::getEditEnv(const vector& path_comps, string& env) "at the specified level\n"); return false; } + if (!cfg_path_exists(path_comps, false, true)) { + /* specified path does not exist. + * follow the original implementation and do a "set". + */ + if (!validateSetPath(path_comps)) { + output_user("The specified config path is not valid\n"); + return false; + } + if (!set_cfg_path(path_comps, false)) { + output_user("Failed to create the specified config path\n"); + return false; + } + } SAVE_PATHS; append_cfg_path(path_comps); append_tmpl_path(path_comps); @@ -775,117 +784,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) bool Cstore::setCfgPath(const vector& path_comps) { - vector ppath; - vtw_def def; - bool ret = true; - bool path_exists = true; - // do the set from the top down - SAVE_PATHS; - for (unsigned int i = 0; i < path_comps.size(); i++) { - // partial path - ppath.push_back(path_comps[i]); - - // get template at this level - if (!get_parsed_tmpl(ppath, false, def)) { - output_user("paths[%s,%s]\n", cfg_path_to_str().c_str(), - tmpl_path_to_str().c_str()); - for (unsigned int i = 0; i < ppath.size(); i++) { - output_user(" [%s]\n", ppath[i].c_str()); - } - exit_internal("failed to get tmpl during set. not validate first?\n"); - } - - // nop if this level already in working (including deactivated) - if (cfg_path_exists(ppath, false, true)) { - continue; - } - path_exists = false; - - // this level not in working. set it. - append_cfg_path(ppath); - append_tmpl_path(ppath); - - if (!def.is_value) { - // this level is a "node" - if (!add_node() || !create_default_children()) { - ret = false; - break; - } - } else if (def.tag) { - // this level is a "tag value". - // add the tag, taking the max tag limit into consideration. - if (!add_tag(def) || !create_default_children()) { - ret = false; - break; - } - } else { - // this level is a "value" of a single-/multi-value node. - // go up 1 level to get the node. - pop_cfg_path(); - if (def.multi) { - // value of multi-value node. - // add the value, taking the max multi limit into consideration. - if (!add_value_to_multi(def, ppath.back())) { - ret = false; - break; - } - } else { - // value of single-value node - if (!write_value(ppath.back())) { - ret = false; - break; - } - } - } - RESTORE_PATHS; - } - RESTORE_PATHS; // if "break" was hit - - if (ret && def.is_value && def.def_default) { - /* a node with default has been explicitly set. needs to be marked - * as non-default for display purposes. - * - * note: when the new value is the same as the old value, the behavior - * is different from before, which was a nop. the new behavior is - * that the value will remain unchanged, but the "default status" - * will be changed, i.e., it will be marked as non-default. - */ - append_cfg_path(path_comps); - pop_cfg_path(); - // only do it if it's previously marked default - if (marked_display_default(false)) { - ret = unmark_display_default(); - - /* XXX work around current commit's unionfs implementation problem. - * current commit's unionfs implementation looks at the "changes only" - * directory (i.e., the r/w portion of the union mount), which is wrong. - * - * all config information should be obtained from two directories: - * "active" and "working", e.g., instead of looking at whiteout files - * in "changes only" to find deleted nodes, nodes that are in "active" - * but not in "working" have been deleted. - * - * in this particular case, commit looks at "changes only" to read the - * node.val file. however, since the value didn't change (only the - * "default status" changed), node.val doesn't appear in "changes only". - * here we re-write the file to force it into "changes only" so that - * commit can work correctly. - */ - vector vvec; - read_value_vec(vvec, false); - write_value_vec(vvec); - - // pretend it didn't exist since we changed the status - path_exists = false; - } - RESTORE_PATHS; - } - if (path_exists) { - // whole path already exists - output_user("The specified configuration node already exists\n"); - // treat as success - } - return ret; + return set_cfg_path(path_comps, true); } /* check if specified "arguments" is valid for "rename" operation @@ -2220,6 +2119,129 @@ Cstore::cfg_path_exists(const vector& path_comps, return ret; } +/* set specified "logical path" in "working config". + * output: whether to generate output + * return true if successful. otherwise return false. + * note: assume specified path is valid (i.e., validateSetPath()). + */ +bool +Cstore::set_cfg_path(const vector& path_comps, bool output) +{ + vector ppath; + vtw_def def; + bool ret = true; + bool path_exists = true; + // do the set from the top down + SAVE_PATHS; + for (unsigned int i = 0; i < path_comps.size(); i++) { + // partial path + ppath.push_back(path_comps[i]); + + // get template at this level + if (!get_parsed_tmpl(ppath, false, def)) { + output_internal("paths[%s,%s]\n", cfg_path_to_str().c_str(), + tmpl_path_to_str().c_str()); + for (unsigned int i = 0; i < ppath.size(); i++) { + output_internal(" [%s]\n", ppath[i].c_str()); + } + exit_internal("failed to get tmpl during set. not validate first?\n"); + } + + // nop if this level already in working (including deactivated) + if (cfg_path_exists(ppath, false, true)) { + continue; + } + path_exists = false; + + // this level not in working. set it. + append_cfg_path(ppath); + append_tmpl_path(ppath); + + if (!def.is_value) { + // this level is a "node" + if (!add_node() || !create_default_children()) { + ret = false; + break; + } + } else if (def.tag) { + // this level is a "tag value". + // add the tag, taking the max tag limit into consideration. + if (!add_tag(def) || !create_default_children()) { + ret = false; + break; + } + } else { + // this level is a "value" of a single-/multi-value node. + // go up 1 level to get the node. + pop_cfg_path(); + if (def.multi) { + // value of multi-value node. + // add the value, taking the max multi limit into consideration. + if (!add_value_to_multi(def, ppath.back())) { + ret = false; + break; + } + } else { + // value of single-value node + if (!write_value(ppath.back())) { + ret = false; + break; + } + } + } + RESTORE_PATHS; + } + RESTORE_PATHS; // if "break" was hit + + if (ret && def.is_value && def.def_default) { + /* a node with default has been explicitly set. needs to be marked + * as non-default for display purposes. + * + * note: when the new value is the same as the old value, the behavior + * is different from before, which was a nop. the new behavior is + * that the value will remain unchanged, but the "default status" + * will be changed, i.e., it will be marked as non-default. + */ + append_cfg_path(path_comps); + pop_cfg_path(); + // only do it if it's previously marked default + if (marked_display_default(false)) { + ret = unmark_display_default(); + + /* XXX work around current commit's unionfs implementation problem. + * current commit's unionfs implementation looks at the "changes only" + * directory (i.e., the r/w portion of the union mount), which is wrong. + * + * all config information should be obtained from two directories: + * "active" and "working", e.g., instead of looking at whiteout files + * in "changes only" to find deleted nodes, nodes that are in "active" + * but not in "working" have been deleted. + * + * in this particular case, commit looks at "changes only" to read the + * node.val file. however, since the value didn't change (only the + * "default status" changed), node.val doesn't appear in "changes only". + * here we re-write the file to force it into "changes only" so that + * commit can work correctly. + */ + vector vvec; + read_value_vec(vvec, false); + write_value_vec(vvec); + + // pretend it didn't exist since we changed the status + path_exists = false; + } + RESTORE_PATHS; + } + if (path_exists) { + // whole path already exists + if (output) { + output_user("The specified configuration node already exists\n"); + } + // treat as success + } + return ret; +} + /* remove tag at current work path and its subtree. * if specified tag is the last one, also remove the tag node. * return true if successful. otherwise return false. diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp index f34a8af..34193c2 100644 --- a/src/cstore/cstore.hpp +++ b/src/cstore/cstore.hpp @@ -373,6 +373,7 @@ private: vector& rn_args); bool cfg_path_exists(const vector& path_comps, bool active_cfg, bool include_deactivated); + bool set_cfg_path(const vector& path_comps, bool output); // these operate on current work path (or active with "active_cfg") bool remove_tag(); -- cgit v1.2.3 From 20e764c614aaee654bbe492378018aae9327c9f5 Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Mon, 2 Aug 2010 14:48:35 -0700 Subject: eval "allowed:" script in template * this emulates the original behavior that "allowed:" script is "eval"ed rather than "exec"ed. --- src/cstore/cstore.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/cstore') diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp index 28c9d63..e5de5b4 100644 --- a/src/cstore/cstore.cpp +++ b/src/cstore/cstore.cpp @@ -626,7 +626,11 @@ Cstore::getCompletionEnv(const vector& comps, string& env) if (def.def_enumeration) { cmd_str += (C_ENUM_SCRIPT_DIR + "/" + def.def_enumeration); } else { - cmd_str += def.def_allowed; + string astr = def.def_allowed; + shell_escape_squotes(astr); + cmd_str += "_cstore_internal_allowed () { eval '"; + cmd_str += astr; + cmd_str += "'; }; _cstore_internal_allowed"; } char *buf = (char *) malloc(MAX_CMD_OUTPUT_SIZE); -- cgit v1.2.3 From 9719a625221cc3e74e4c73c4ae223456987d0ac1 Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Tue, 3 Aug 2010 18:56:30 -0700 Subject: remove "def" and "node.val" usage from high-level operations. --- lib/Vyatta/ConfigLoad.pm | 12 ++- lib/Vyatta/ConfigOutput.pm | 136 +++++++++++----------------------- src/cstore/unionfs/cstore-unionfs.cpp | 10 +-- 3 files changed, 50 insertions(+), 108 deletions(-) (limited to 'src/cstore') diff --git a/lib/Vyatta/ConfigLoad.pm b/lib/Vyatta/ConfigLoad.pm index 55ba76b..960240c 100755 --- a/lib/Vyatta/ConfigLoad.pm +++ b/lib/Vyatta/ConfigLoad.pm @@ -290,15 +290,13 @@ sub findDeletedNodes { my $new_ref = $_[0]; my @active_path = @{$_[1]}; $active_cfg->setLevel(join ' ', @active_path); + if ($active_cfg->isLeafNode()) { + findDeletedValues($new_ref, \@active_path); + return; + } + # not a leaf node my @active_nodes = $active_cfg->listOrigNodesDA(); foreach (@active_nodes) { - if ($_ eq 'def') { - next; - } - if ($_ eq 'node.val') { - findDeletedValues($new_ref, \@active_path); - next; - } if (!defined($new_ref->{$_})) { my @plist = applySingleQuote(@active_path, $_); push @delete_list, [\@plist, 0]; diff --git a/lib/Vyatta/ConfigOutput.pm b/lib/Vyatta/ConfigOutput.pm index acb2bb3..604d35e 100755 --- a/lib/Vyatta/ConfigOutput.pm +++ b/lib/Vyatta/ConfigOutput.pm @@ -129,6 +129,11 @@ sub displayValues { print "$dis$diff$prefix$name $nval\n"; } } else { + if ($config->isDefault() and !$show_all) { + # not going to show anything so just return + return; + } + my $oval = $config->returnOrigValueDA(); my $nval = $config->returnValueDA(); if ($is_text) { @@ -140,17 +145,11 @@ sub displayValues { } } - my %cnodes = $config->listNodeStatusDA(); - my @cnames = sort keys %cnodes; - if (defined($simple_show)) { - if (!defined($cnodes{'def'}) or $cnodes{'def'} eq 'deleted' - or $show_all) { - if ($is_password && $hide_password) { - $oval = $HIDE_PASSWORD; - } - print "$dis$prefix$name $oval\n"; + if ($is_password && $hide_password) { + $oval = $HIDE_PASSWORD; } + print "$dis$prefix$name $oval\n"; return; } my $value = $nval; @@ -166,14 +165,10 @@ sub displayValues { $diff = '>'; } } - # also need to handle the case where def marker is deleted. - if (!defined($cnodes{'def'}) or $cnodes{'def'} eq 'deleted' - or $show_all) { - if ($is_password && $hide_password) { - $value = $HIDE_PASSWORD; - } - print "$dis$diff$prefix$name $value\n"; + if ($is_password && $hide_password) { + $value = $HIDE_PASSWORD; } + print "$dis$diff$prefix$name $value\n"; } } @@ -196,15 +191,9 @@ sub displayDeletedOrigChildren { for my $child (sort @children) { # reset level $config->setLevel(''); - - if ($child eq 'node.val') { - # should not happen! - next; - } - my $is_tag = $config->isTagNode(join(' ', @cur_path, $child)); - if (!defined $is_tag) { + if (!$is_tag) { my $path = join(' ',( @cur_path, $child )); my $comment = $config->returnComment($path); if (defined $comment) { @@ -238,25 +227,17 @@ sub displayDeletedOrigChildren { } $config->setLevel(join ' ', (@cur_path, $child)); - - # remove listOrigNodesNoDef so it's one fewer function that uses - # the cstore implementation details at the perl API level. - my @cnames = grep(!/^def$/, sort($config->listOrigNodesDA())); - - if ($cnames[0] eq 'node.val') { + if ($config->isLeafNode()) { displayValues([ @cur_path, $child ], $dis, $prefix, $child, $dont_show_as_deleted); - } elsif ($cnames[0] eq 'def') { - #ignore - } elsif (scalar($#cnames) >= 0) { + next; + } + + # not a leaf node + my @cnames = sort versioncmp ($config->listOrigNodesDA()); + if (scalar(@cnames) > 0) { if ($is_tag) { - @cnames = sort versioncmp @cnames; foreach my $cname (@cnames) { - if ($cname eq 'node.val') { - # should not happen - next; - } - my $path = join(' ',( @cur_path, $child, $cname )); $config->setLevel($path); @@ -319,14 +300,6 @@ sub displayChildren { my $prefix = $_[3]; for my $child (sort (keys %child_hash)) { my $dis = ""; - my @tmp = @cur_path; - push (@tmp,$child); - - if ($child eq 'node.val') { - # should not happen! - next; - } - my ($diff, $vdiff) = (' ', ' '); if ($child_hash{$child} eq 'added') { $diff = '+'; @@ -341,7 +314,7 @@ sub displayChildren { $config->setLevel(''); my $is_tag = $config->isTagNode(join(' ', @cur_path, $child)); - if (!defined($is_tag)) { + if (!$is_tag) { my $path = join(' ',( @cur_path, $child )); my $comment = $config->returnComment($path); if (defined $comment) { @@ -375,32 +348,18 @@ sub displayChildren { } $config->setLevel(join ' ', (@cur_path, $child)); + if ($config->isLeafNode()) { + displayValues([ @cur_path, $child ], $dis, $prefix, $child); + next; + } + + # not a leaf node my %cnodes = $config->listNodeStatusDA(); my @cnames = sort keys %cnodes; - - #if node.val exists and ct == 0 w/o def or ct ==1 w/ def - my $leaf = 0; - if ($cnodes{'def'}) { - if ($#cnames == 1 && $cnodes{'node.val'}) { - $leaf = 1; - } - } else { - if ($#cnames == 0 && $cnodes{'node.val'}) { - $leaf = 1; - } - } - - if ($leaf == 1) { - displayValues([ @cur_path, $child ], $dis, $prefix, $child); - } elsif (scalar($#cnames) >= 0) { + if (scalar(@cnames) > 0) { if ($is_tag) { @cnames = sort versioncmp @cnames; foreach my $cname (@cnames) { - if ($cname eq 'node.val') { - # should not happen - next; - } - my $path = join(' ',( @cur_path, $child, $cname )); $config->setLevel($path); my $comment = $config->returnComment(); @@ -455,21 +414,24 @@ sub displayChildren { print "$dis$diff$prefix$child {\n"; if ($child_hash{$child} eq 'deleted') { # this should not happen - displayDeletedOrigChildren([ @cur_path, $child ], $dis, "$prefix "); + displayDeletedOrigChildren([ @cur_path, $child ], $dis, + "$prefix "); } else { - displayChildren(\%cnodes, [ @cur_path, $child ], $dis, "$prefix "); + displayChildren(\%cnodes, [ @cur_path, $child ], $dis, + "$prefix "); } print "$dis$diff$prefix}\n"; } } else { if ($child_hash{$child} eq 'deleted') { + # XXX weird. already checked for leaf node above. $config->setLevel(''); - my @onodes = $config->listOrigNodesDA(join ' ', (@cur_path, $child)); - if ($#onodes == 0 && $onodes[0] eq 'node.val') { + if ($config->isLeafNode(join ' ', (@cur_path, $child))) { displayValues([ @cur_path, $child ], $dis, $prefix, $child); } else { print "$dis$diff$prefix$child {\n"; - displayDeletedOrigChildren([ @cur_path, $child ], $dis, "$prefix "); + displayDeletedOrigChildren([ @cur_path, $child ], $dis, + "$prefix "); print "$dis$diff$prefix}\n"; } } else { @@ -487,29 +449,15 @@ sub displayChildren { sub outputNewConfig { $config = new Vyatta::Config; $config->setLevel(join ' ', @_); - my %rnodes = $config->listNodeStatusDA(); + if ($config->isLeafNode()) { + displayValues([ @_ ], '', '', $_[$#_]); + return; + } + # not a leaf node + my %rnodes = $config->listNodeStatusDA(); if (scalar(keys %rnodes) > 0) { - my @rn = keys %rnodes; - - #if node.val exists and ct == 0 w/o def or ct ==1 w/ def - my $leaf = 0; - if ($rnodes{'def'}) { - if ($#rn == 1 && $rnodes{'node.val'}) { - $leaf = 1; - } - } else { - if ($#rn == 0 && $rnodes{'node.val'}) { - $leaf = 1; - } - } - - if ($leaf == 1) { - # this is a leaf value-node - displayValues([ @_ ], '', '', $_[$#_]); - } else { - displayChildren(\%rnodes, [ @_ ], '', ''); - } + displayChildren(\%rnodes, [ @_ ], '', ''); } else { if ($config->existsOrig() && ! $config->exists()) { # this is a deleted node diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp index f2bcf46..3ac6052 100644 --- a/src/cstore/unionfs/cstore-unionfs.cpp +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -488,14 +488,10 @@ UnionfsCstore::get_all_child_node_names_impl(vector& cnodes, * use a whilelist-approach, i.e., only add the following: * node.val * def + * + * FIXED: perl scripts have been changed to eliminate the use of "def" + * and "node.val", so they no longer need to be returned. */ - if (b_fs_exists(p / C_VAL_NAME) && b_fs_is_regular(p / C_VAL_NAME)) { - cnodes.push_back(C_VAL_NAME); - } - if (b_fs_exists(p / C_MARKER_DEF_VALUE) - && b_fs_is_regular(p / C_MARKER_DEF_VALUE)) { - cnodes.push_back(C_MARKER_DEF_VALUE); - } } bool -- cgit v1.2.3 From 97bf9794e381317ca89d15009b6827dc6ff9d78c Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Mon, 9 Aug 2010 17:20:50 -0700 Subject: provide default edit level --- src/cstore/unionfs/cstore-unionfs.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/cstore') diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp index 3ac6052..1cfccd4 100644 --- a/src/cstore/unionfs/cstore-unionfs.cpp +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -192,6 +192,7 @@ UnionfsCstore::UnionfsCstore(bool use_edit_level) * from environment. only the actual CLI operations use them. * so here make it an option. */ + mutable_cfg_path = "/"; if (use_edit_level) { // set up path strings if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) { @@ -241,6 +242,7 @@ UnionfsCstore::UnionfsCstore(const string& sid, string& env) // set up path strings using level vars char *val; + mutable_cfg_path = "/"; if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) { mutable_cfg_path = val; } -- cgit v1.2.3 From 722f67e407b5a1cf8aa060a40355982176870ede Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Mon, 9 Aug 2010 18:52:23 -0700 Subject: add function to C API --- src/cstore/cstore-c.cpp | 58 ++++++++++++++++++++++++++++++++++++------------- src/cstore/cstore-c.h | 4 ++++ 2 files changed, 47 insertions(+), 15 deletions(-) (limited to 'src/cstore') diff --git a/src/cstore/cstore-c.cpp b/src/cstore/cstore-c.cpp index 0e25b3c..95f052b 100644 --- a/src/cstore/cstore-c.cpp +++ b/src/cstore/cstore-c.cpp @@ -21,6 +21,14 @@ #include #include +static void +_get_str_vec(vector& vec, const char *strs[], int num_strs) +{ + for (int i = 0; i < num_strs; i++) { + vec.push_back(strs[i]); + } +} + void * cstore_init(void) { @@ -41,9 +49,7 @@ cstore_validate_tmpl_path(void *handle, const char *path_comps[], { if (handle) { vector vs; - for (int i = 0; i < num_comps; i++) { - vs.push_back(path_comps[i]); - } + _get_str_vec(vs, path_comps, num_comps); Cstore *cs = (Cstore *) handle; return (cs->validateTmplPath(vs, validate_tags) ? 1 : 0); } @@ -56,9 +62,7 @@ cstore_validate_tmpl_path_d(void *handle, const char *path_comps[], { if (handle) { vector vs; - for (int i = 0; i < num_comps; i++) { - vs.push_back(path_comps[i]); - } + _get_str_vec(vs, path_comps, num_comps); Cstore *cs = (Cstore *) handle; return (cs->validateTmplPath(vs, validate_tags, *def) ? 1 : 0); } @@ -70,9 +74,7 @@ cstore_cfg_path_exists(void *handle, const char *path_comps[], int num_comps) { if (handle) { vector vs; - for (int i = 0; i < num_comps; i++) { - vs.push_back(path_comps[i]); - } + _get_str_vec(vs, path_comps, num_comps); Cstore *cs = (Cstore *) handle; return (cs->cfgPathExists(vs) ? 1 : 0); } @@ -108,15 +110,39 @@ cstore_cfg_path_deactivated(void *handle, const char *path_comps[], { if (handle) { vector vs; - for (int i = 0; i < num_comps; i++) { - vs.push_back(path_comps[i]); - } + _get_str_vec(vs, path_comps, num_comps); Cstore *cs = (Cstore *) handle; return (cs->cfgPathDeactivated(vs, in_active) ? 1 : 0); } return 0; } +char * +cstore_cfg_path_get_effective_value(void *handle, const char *path_comps[], + int num_comps) +{ + if (handle) { + vector vs; + _get_str_vec(vs, path_comps, num_comps); + Cstore *cs = (Cstore *) handle; + string val; + if (!cs->cfgPathGetEffectiveValue(vs, val)) { + return NULL; + } + + int vsize = val.length(); + char *buf = (char *) malloc(vsize + 1); + if (!buf) { + return NULL; + } + strncpy(buf, val.c_str(), vsize); + buf[vsize] = 0; + + return buf; + } + return NULL; +} + char ** cstore_path_string_to_path_comps(const char *path_str, int *num_comps) { @@ -141,10 +167,12 @@ cstore_path_string_to_path_comps(const char *path_str, int *num_comps) vec.push_back(start); } char **ret = (char **) malloc(sizeof(char *) * vec.size()); - for (unsigned int i = 0; i < vec.size(); i++) { - ret[i] = strdup(vec[i].c_str()); + if (ret) { + for (unsigned int i = 0; i < vec.size(); i++) { + ret[i] = strdup(vec[i].c_str()); + } + *num_comps = vec.size(); } - *num_comps = vec.size(); free(pstr); return ret; } diff --git a/src/cstore/cstore-c.h b/src/cstore/cstore-c.h index c2c5fa0..a2ad844 100644 --- a/src/cstore/cstore-c.h +++ b/src/cstore/cstore-c.h @@ -34,6 +34,10 @@ int cstore_cfg_path_exists(void *handle, const char *path_comps[], int cstore_cfg_path_deactivated(void *handle, const char *path_comps[], int num_comps, int in_active); +char *cstore_cfg_path_get_effective_value(void *handle, + const char *path_comps[], + int num_comps); + /* the following are internal APIs for the library. they can only be used * during cstore operations since they operate on "current" paths constructed * by the operations. -- cgit v1.2.3 From ced5e57f16b055249be5012bf13bb90c8811eb2a Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Thu, 12 Aug 2010 12:03:54 -0700 Subject: add more functions to shell API --- src/cli_shell_api.cpp | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/cstore/cstore.hpp | 8 +++++--- 2 files changed, 62 insertions(+), 3 deletions(-) (limited to 'src/cstore') diff --git a/src/cli_shell_api.cpp b/src/cli_shell_api.cpp index 7f0a4d1..f0ac34f 100644 --- a/src/cli_shell_api.cpp +++ b/src/cli_shell_api.cpp @@ -212,6 +212,14 @@ existsActive(const vector& args) exit(cstore.cfgPathExists(args, true) ? 0 : 1); } +/* same as isEffective() in Perl API */ +static void +existsEffective(const vector& args) +{ + UnionfsCstore cstore(true); + exit(cstore.cfgPathEffective(args) ? 0 : 1); +} + /* same as listNodes() in Perl API. * * outputs a string representing multiple nodes. this string MUST be @@ -247,6 +255,20 @@ listActiveNodes(const vector& args) print_vec(cnodes, " ", "'"); } +/* same as listEffectiveNodes() in Perl API. + * + * outputs a string representing multiple nodes. this string MUST be + * "eval"ed into an array of nodes. see listNodes above. + */ +static void +listEffectiveNodes(const vector& args) +{ + UnionfsCstore cstore(true); + vector cnodes; + cstore.cfgPathGetEffectiveChildNodes(args, cnodes); + print_vec(cnodes, " ", "'"); +} + /* same as returnValue() in Perl API. outputs a string. */ static void returnValue(const vector& args) @@ -271,6 +293,18 @@ returnActiveValue(const vector& args) printf("%s", val.c_str()); } +/* same as returnEffectiveValue() in Perl API. outputs a string. */ +static void +returnEffectiveValue(const vector& args) +{ + UnionfsCstore cstore(true); + string val; + if (!cstore.cfgPathGetEffectiveValue(args, val)) { + exit(1); + } + printf("%s", val.c_str()); +} + /* same as returnValues() in Perl API. * * outputs a string representing multiple values. this string MUST be @@ -318,6 +352,22 @@ returnActiveValues(const vector& args) print_vec(vvec, " ", "'"); } +/* same as returnEffectiveValues() in Perl API. + * + * outputs a string representing multiple values. this string MUST be + * "eval"ed into an array of values. see returnValues above. + */ +static void +returnEffectiveValues(const vector& args) +{ + UnionfsCstore cstore(true); + vector vvec; + if (!cstore.cfgPathGetEffectiveValues(args, vvec)) { + exit(1); + } + print_vec(vvec, " ", "'"); +} + #define OP(name, exact, exact_err, min, min_err) \ { #name, exact, exact_err, min, min_err, &name } @@ -343,12 +393,19 @@ static OpT ops[] = { OP(exists, -1, NULL, 1, "Must specify config path"), OP(existsActive, -1, NULL, 1, "Must specify config path"), + OP(existsEffective, -1, NULL, 1, "Must specify config path"), + OP(listNodes, -1, NULL, -1, NULL), OP(listActiveNodes, -1, NULL, -1, NULL), + OP(listEffectiveNodes, -1, NULL, 1, "Must specify config path"), + OP(returnValue, -1, NULL, 1, "Must specify config path"), OP(returnActiveValue, -1, NULL, 1, "Must specify config path"), + OP(returnEffectiveValue, -1, NULL, 1, "Must specify config path"), + OP(returnValues, -1, NULL, 1, "Must specify config path"), OP(returnActiveValues, -1, NULL, 1, "Must specify config path"), + OP(returnEffectiveValues, -1, NULL, 1, "Must specify config path"), {NULL, -1, NULL, -1, NULL, NULL} }; diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp index 34193c2..caf845b 100644 --- a/src/cstore/cstore.hpp +++ b/src/cstore/cstore.hpp @@ -174,6 +174,7 @@ public: bool active_cfg = false); bool cfgPathDefault(const vector& path_comps, bool active_cfg = false); + /* observers for working AND active configs (at the same time). * MUST ONLY be used during config session. */ @@ -184,9 +185,10 @@ public: vector& cnodes); void cfgPathGetChildNodesStatus(const vector& path_comps, map& cmap); - /* observers for "effective config" (a combination of working config, - * active config, and commit processing state) only. - * MUST ONLY be used during config session. + + /* observers for "effective config". can be used both during a config + * session and outside a config session. more detailed information + * can be found in the source file. */ bool cfgPathEffective(const vector& path_comps); void cfgPathGetEffectiveChildNodes(const vector& path_comps, -- cgit v1.2.3 From 5ed312ef6122c7f652c0c07e4357721b4dda8f4f Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Fri, 13 Aug 2010 10:56:03 -0700 Subject: change all vector/string size to size_t just to be safe. * would have been a problem if template tree becomes more than 2^32 levels deep or if value strings longer than 2^32 characters are allowed. --- src/cstore/cstore-c.cpp | 4 +-- src/cstore/cstore-varref.cpp | 8 ++--- src/cstore/cstore.cpp | 58 +++++++++++++++++------------------ src/cstore/unionfs/cstore-unionfs.cpp | 14 ++++----- src/cstore/unionfs/cstore-unionfs.hpp | 2 +- 5 files changed, 43 insertions(+), 43 deletions(-) (limited to 'src/cstore') diff --git a/src/cstore/cstore-c.cpp b/src/cstore/cstore-c.cpp index 95f052b..fd818a5 100644 --- a/src/cstore/cstore-c.cpp +++ b/src/cstore/cstore-c.cpp @@ -150,7 +150,7 @@ cstore_path_string_to_path_comps(const char *path_str, int *num_comps) size_t len = strlen(pstr); vector vec; char *start = NULL; - for (unsigned int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { if (pstr[i] == '/') { if (start) { pstr[i] = 0; @@ -168,7 +168,7 @@ cstore_path_string_to_path_comps(const char *path_str, int *num_comps) } char **ret = (char **) malloc(sizeof(char *) * vec.size()); if (ret) { - for (unsigned int i = 0; i < vec.size(); i++) { + for (size_t i = 0; i < vec.size(); i++) { ret[i] = strdup(vec[i].c_str()); } *num_comps = vec.size(); diff --git a/src/cstore/cstore-varref.cpp b/src/cstore/cstore-varref.cpp index 46950fa..f00ed38 100644 --- a/src/cstore/cstore-varref.cpp +++ b/src/cstore/cstore-varref.cpp @@ -166,7 +166,7 @@ Cstore::VarRef::process_ref(const vector& ref_comps, // tag node vector cnodes; _cstore->cfgPathGetChildNodes(pcomps, cnodes, _active); - for (unsigned int i = 0; i < cnodes.size(); i++) { + for (size_t i = 0; i < cnodes.size(); i++) { pcomps.push_back(cnodes[i]); process_ref(rcomps, pcomps, def.def_type); pcomps.pop_back(); @@ -198,7 +198,7 @@ Cstore::VarRef::process_ref(const vector& ref_comps, return; } string val; - for (unsigned int i = 0; i < vals.size(); i++) { + for (size_t i = 0; i < vals.size(); i++) { if (val.length() > 0) { val += " "; } @@ -227,7 +227,7 @@ Cstore::VarRef::getValue(string& value, vtw_type_e& def_type) vector result; map added; def_type = ERROR_TYPE; - for (unsigned int i = 0; i < _paths.size(); i++) { + for (size_t i = 0; i < _paths.size(); i++) { if (_paths[i].first.size() == 0) { // empty path continue; @@ -259,7 +259,7 @@ Cstore::VarRef::getValue(string& value, vtw_type_e& def_type) def_type = TEXT_TYPE; } value = ""; - for (unsigned int i = 0; i < result.size(); i++) { + for (size_t i = 0; i < result.size(); i++) { if (i > 0) { value += " "; } diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp index e5de5b4..04e8b27 100644 --- a/src/cstore/cstore.cpp +++ b/src/cstore/cstore.cpp @@ -565,7 +565,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) // return all template children get_all_tmpl_child_node_names(ufvec); } - for (unsigned int i = 0; i < ufvec.size(); i++) { + for (size_t i = 0; i < ufvec.size(); i++) { if (last_comp == "" || ufvec[i].compare(0, last_comp.length(), last_comp) == 0) { comp_vals.push_back(ufvec[i]); @@ -576,7 +576,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) break; } sort(comp_vals.begin(), comp_vals.end()); - for (unsigned int i = 0; i < comp_vals.size(); i++) { + for (size_t i = 0; i < comp_vals.size(); i++) { pair hpair(comp_vals[i], ""); push_tmpl_path(hpair.first); vtw_def cdef; @@ -619,7 +619,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) string cmd_str = ("export " + C_ENV_SHELL_CWORD_COUNT + "=" + cword_count.str() + "; "); cmd_str += ("export " + C_ENV_SHELL_CWORDS + "=("); - for (unsigned int i = 0; i < comps.size(); i++) { + for (size_t i = 0; i < comps.size(); i++) { cmd_str += (" '" + comps[i] + "'"); } cmd_str += "); "; @@ -677,7 +677,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) } if (def.def_val_help) { // has val_help. first separate individual lines. - unsigned int start = 0, i = 0; + size_t start = 0, i = 0; vector vhelps; for (i = 0; def.def_val_help[i]; i++) { if (def.def_val_help[i] == '\n') { @@ -720,7 +720,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) // this var is the array of possible completions env = (C_ENV_SHAPI_COMP_VALS + "=("); - for (unsigned int i = 0; i < comp_vals.size(); i++) { + for (size_t i = 0; i < comp_vals.size(); i++) { shell_escape_squotes(comp_vals[i]); env += ("'" + comp_vals[i] + "' "); } @@ -753,7 +753,7 @@ Cstore::getCompletionEnv(const vector& comps, string& env) string hitems = (C_ENV_SHAPI_HELP_ITEMS + "=("); // this var is the array of "help strings" corresponding to the items string hstrs = (C_ENV_SHAPI_HELP_STRS + "=("); - for (unsigned int i = 0; i < help_pairs.size(); i++) { + for (size_t i = 0; i < help_pairs.size(); i++) { string hi = help_pairs[i].first; string hs = help_pairs[i].second; shell_escape_squotes(hi); @@ -1074,10 +1074,10 @@ Cstore::cfgPathGetDeletedChildNodesDA(const vector& path_comps, vector wcnodes; cfgPathGetChildNodesDA(path_comps, wcnodes, false, include_deactivated); map cmap; - for (unsigned int i = 0; i < wcnodes.size(); i++) { + for (size_t i = 0; i < wcnodes.size(); i++) { cmap[wcnodes[i]] = true; } - for (unsigned int i = 0; i < acnodes.size(); i++) { + for (size_t i = 0; i < acnodes.size(); i++) { if (cmap.find(acnodes[i]) == cmap.end()) { // in active but not in working cnodes.push_back(acnodes[i]); @@ -1102,10 +1102,10 @@ Cstore::cfgPathGetChildNodesStatus(const vector& path_comps, vector wcnodes; cfgPathGetChildNodes(path_comps, acnodes, true); cfgPathGetChildNodes(path_comps, wcnodes, false); - for (unsigned int i = 0; i < acnodes.size(); i++) { + for (size_t i = 0; i < acnodes.size(); i++) { umap[acnodes[i]] = true; } - for (unsigned int i = 0; i < wcnodes.size(); i++) { + for (size_t i = 0; i < wcnodes.size(); i++) { umap[wcnodes[i]] = true; } @@ -1141,7 +1141,7 @@ Cstore::cfgPathGetChildNodesStatusDA(const vector& path_comps, // process deleted nodes first vector del_nodes; cfgPathGetDeletedChildNodesDA(path_comps, del_nodes); - for (unsigned int i = 0; i < del_nodes.size(); i++) { + for (size_t i = 0; i < del_nodes.size(); i++) { cmap[del_nodes[i]] = C_NODE_STATUS_DELETED; } @@ -1149,7 +1149,7 @@ Cstore::cfgPathGetChildNodesStatusDA(const vector& path_comps, vector work_nodes; cfgPathGetChildNodesDA(path_comps, work_nodes, false); vector ppath = path_comps; - for (unsigned int i = 0; i < work_nodes.size(); i++) { + for (size_t i = 0; i < work_nodes.size(); i++) { ppath.push_back(work_nodes[i]); /* note: in the DA version here, we do NOT check the deactivate state * when considering the state of the child nodes (which include @@ -1183,7 +1183,7 @@ bool Cstore::cfgPathDeactivated(const vector& path_comps, bool active_cfg) { vector ppath; - for (unsigned int i = 0; i < path_comps.size(); i++) { + for (size_t i = 0; i < path_comps.size(); i++) { ppath.push_back(path_comps[i]); if (cfgPathMarkedDeactivated(ppath, active_cfg)) { // an ancestor or itself is marked deactivated @@ -1498,10 +1498,10 @@ Cstore::cfgPathGetEffectiveChildNodes(const vector& path_comps, vector wcnodes; cfgPathGetChildNodes(path_comps, acnodes, true); cfgPathGetChildNodes(path_comps, wcnodes, false); - for (unsigned int i = 0; i < acnodes.size(); i++) { + for (size_t i = 0; i < acnodes.size(); i++) { cmap[acnodes[i]] = true; } - for (unsigned int i = 0; i < wcnodes.size(); i++) { + for (size_t i = 0; i < wcnodes.size(); i++) { cmap[wcnodes[i]] = true; } @@ -1583,10 +1583,10 @@ Cstore::cfgPathGetEffectiveValues(const vector& path_comps, vector nvals; cfgPathGetValues(path_comps, ovals, true); cfgPathGetValues(path_comps, nvals, false); - for (unsigned int i = 0; i < ovals.size(); i++) { + for (size_t i = 0; i < ovals.size(); i++) { vmap[ovals[i]] = true; } - for (unsigned int i = 0; i < nvals.size(); i++) { + for (size_t i = 0; i < nvals.size(); i++) { vmap[nvals[i]] = true; } @@ -1728,7 +1728,7 @@ Cstore::markCfgPathChanged(const vector& path_comps) // now mark each level as changed vector ppath; - for (unsigned int i = 0; i < path_comps.size(); i++) { + for (size_t i = 0; i < path_comps.size(); i++) { ppath.push_back(path_comps[i]); if (!cfg_path_exists(ppath, false, false)) { // this level no longer in working. nothing further. @@ -1800,7 +1800,7 @@ bool Cstore::append_tmpl_path(const vector& path_comps, bool& is_tag) { is_tag = false; - for (unsigned int i = 0; i < path_comps.size(); i++) { + for (size_t i = 0; i < path_comps.size(); i++) { push_tmpl_path(path_comps[i]); if (tmpl_node_exists()) { // got exact match. continue to next component. @@ -1893,7 +1893,7 @@ Cstore::get_parsed_tmpl(const vector& path_comps, bool validate_vals, */ // first scan up to "full path - 1" bool valid = true; - for (unsigned int i = 0; i < (pcomps->size() - 1); i++) { + for (size_t i = 0; i < (pcomps->size() - 1); i++) { if ((*pcomps)[i] == "") { // only the last component is potentially allowed to be empty str valid = false; @@ -2080,12 +2080,12 @@ Cstore::conv_move_args_for_rename(const vector& args, * * set the extra levels and then just validate as rename */ - unsigned int num_args = args.size(); + size_t num_args = args.size(); if (num_args < 4) { // need at least 4 args return false; } - for (unsigned int i = 0; i < (num_args - 4); i++) { + for (size_t i = 0; i < (num_args - 4); i++) { edit_path_comps.push_back(args[i]); } rn_args.push_back(args[num_args - 4]); // vif @@ -2137,7 +2137,7 @@ Cstore::set_cfg_path(const vector& path_comps, bool output) bool path_exists = true; // do the set from the top down SAVE_PATHS; - for (unsigned int i = 0; i < path_comps.size(); i++) { + for (size_t i = 0; i < path_comps.size(); i++) { // partial path ppath.push_back(path_comps[i]); @@ -2145,7 +2145,7 @@ Cstore::set_cfg_path(const vector& path_comps, bool output) if (!get_parsed_tmpl(ppath, false, def)) { output_internal("paths[%s,%s]\n", cfg_path_to_str().c_str(), tmpl_path_to_str().c_str()); - for (unsigned int i = 0; i < ppath.size(); i++) { + for (size_t i = 0; i < ppath.size(); i++) { output_internal(" [%s]\n", ppath[i].c_str()); } exit_internal("failed to get tmpl during set. not validate first?\n"); @@ -2290,9 +2290,9 @@ Cstore::remove_value_from_multi(const string& value) } // remove the value - unsigned int bc = vvec.size(); + size_t bc = vvec.size(); vector nvec(vvec.begin(), remove(vvec.begin(), vvec.end(), value)); - unsigned int ac = nvec.size(); + size_t ac = nvec.size(); // sanity check if (ac == bc) { @@ -2441,7 +2441,7 @@ Cstore::get_all_child_node_names(vector& cnodes, bool active_cfg, { vector nodes; get_all_child_node_names_impl(nodes, active_cfg); - for (unsigned int i = 0; i < nodes.size(); i++) { + for (size_t i = 0; i < nodes.size(); i++) { if (!include_deactivated) { push_cfg_path(nodes[i]); bool skip = marked_deactivated(active_cfg); @@ -2465,7 +2465,7 @@ Cstore::create_default_children() vector tcnodes; get_all_tmpl_child_node_names(tcnodes); bool ret = true; - for (unsigned int i = 0; i < tcnodes.size(); i++) { + for (size_t i = 0; i < tcnodes.size(); i++) { push_tmpl_path(tcnodes[i]); vtw_def def; if (tmpl_node_exists() && tmpl_parse(def)) { @@ -2496,7 +2496,7 @@ Cstore::get_edit_env(string& env) vector lvec; get_edit_level(lvec); string lvl; - for (unsigned int i = 0; i < lvec.size(); i++) { + for (size_t i = 0; i < lvec.size(); i++) { if (lvl.length() > 0) { lvl += " "; } diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp index 1cfccd4..860d553 100644 --- a/src/cstore/unionfs/cstore-unionfs.cpp +++ b/src/cstore/unionfs/cstore-unionfs.cpp @@ -101,7 +101,7 @@ _escape_path_name(const string& path) // special case for empty string string npath = (path.size() == 0) ? _fs_escape_chars[-1] : ""; - for (unsigned int i = 0; i < path.size(); i++) { + for (size_t i = 0; i < path.size(); i++) { npath += _escape_char(path[i]); } @@ -123,7 +123,7 @@ _unescape_path_name(const string& path) // assume all escape patterns are 3-char string npath = ""; - for (unsigned int i = 0; i < path.size(); i++) { + for (size_t i = 0; i < path.size(); i++) { if ((path.size() - i) < 3) { npath += path.substr(i); break; @@ -514,7 +514,7 @@ UnionfsCstore::read_value_vec(vector& vvec, bool active_cfg) * be writing it any more. */ // separate values using newline as delimiter - unsigned int start_idx = 0, idx = 0; + size_t start_idx = 0, idx = 0; for (; idx < ostr.size(); idx++) { if (ostr[idx] == '\n') { // got a value @@ -547,7 +547,7 @@ UnionfsCstore::write_value_vec(const vector& vvec, bool active_cfg) } string ostr = ""; - for (unsigned int i = 0; i < vvec.size(); i++) { + for (size_t i = 0; i < vvec.size(); i++) { if (i > 0) { // subsequent values require delimiter ostr += "\n"; @@ -734,7 +734,7 @@ UnionfsCstore::unmark_deactivated_descendants() } markers.push_back(di->path()); } - for (unsigned int i = 0; i < markers.size(); i++) { + for (size_t i = 0; i < markers.size(); i++) { b_fs::remove(markers[i]); } } catch (...) { @@ -828,11 +828,11 @@ UnionfsCstore::discard_changes(unsigned long long& num_removed) // remove and count num_removed = 0; - for (unsigned int i = 0; i < files.size(); i++) { + for (size_t i = 0; i < files.size(); i++) { b_fs::remove(files[i]); num_removed++; } - for (unsigned int i = 0; i < directories.size(); i++) { + for (size_t i = 0; i < directories.size(); i++) { num_removed += b_fs::remove_all(directories[i]); } } catch (...) { diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp index b0e7201..9e49064 100644 --- a/src/cstore/unionfs/cstore-unionfs.hpp +++ b/src/cstore/unionfs/cstore-unionfs.hpp @@ -107,7 +107,7 @@ private: return pop_path(mutable_cfg_path); }; void append_cfg_path(const vector& path_comps) { - for (unsigned int i = 0; i < path_comps.size(); i++) { + for (size_t i = 0; i < path_comps.size(); i++) { push_cfg_path(path_comps[i]); } }; -- cgit v1.2.3 From 31f5a0c0245604b891aac418b3b787556b9f6b5b Mon Sep 17 00:00:00 2001 From: An-Cheng Huang Date: Fri, 13 Aug 2010 11:18:33 -0700 Subject: add API function for retrieving deleted values of a multi node. --- lib/Vyatta/Config.pm | 9 +++++++++ perl_dmod/Cstore/Cstore.xs | 11 +++++++++++ src/cstore/cstore.cpp | 29 +++++++++++++++++++++++++++++ src/cstore/cstore.hpp | 2 ++ 4 files changed, 51 insertions(+) (limited to 'src/cstore') diff --git a/lib/Vyatta/Config.pm b/lib/Vyatta/Config.pm index 371fe32..a02ee18 100755 --- a/lib/Vyatta/Config.pm +++ b/lib/Vyatta/Config.pm @@ -356,6 +356,15 @@ sub listDeleted { return @{$ref}; } +## returnDeletedValues("level") +# return array of deleted values of specified "multi node" +sub returnDeletedValues { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetDeletedValues( + $self->get_path_comps($path)); + return @{$ref}; +} + ## isAdded("node") # whether specified node has been added in working config sub isAdded { diff --git a/perl_dmod/Cstore/Cstore.xs b/perl_dmod/Cstore/Cstore.xs index f72a124..4a726e7 100644 --- a/perl_dmod/Cstore/Cstore.xs +++ b/perl_dmod/Cstore/Cstore.xs @@ -189,6 +189,17 @@ OUTPUT: RETVAL +STRVEC * +Cstore::cfgPathGetDeletedValues(STRVEC *vref) +PREINIT: + vector arg_strvec; +CODE: + vector ret_strvec; + THIS->cfgPathGetDeletedValues(arg_strvec, ret_strvec); +OUTPUT: + RETVAL + + STRSTRMAP * Cstore::cfgPathGetChildNodesStatus(STRVEC *vref) PREINIT: diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp index 04e8b27..3cd0649 100644 --- a/src/cstore/cstore.cpp +++ b/src/cstore/cstore.cpp @@ -1085,6 +1085,35 @@ Cstore::cfgPathGetDeletedChildNodesDA(const vector& path_comps, } } +/* get "deleted" values of specified "multi node" during commit + * operation. values are returned in dvals. if specified path is not + * a "multi node", it's a nop. + * + * NOTE: this function does not consider the "value ordering". the "deleted" + * status is purely based on the presence/absence of a value. + */ +void +Cstore::cfgPathGetDeletedValues(const vector& path_comps, + vector& dvals) +{ + vector ovals; + vector nvals; + if (!cfgPathGetValues(path_comps, ovals, true) + || !cfgPathGetValues(path_comps, nvals, false)) { + return; + } + map dmap; + for (size_t i = 0; i < nvals.size(); i++) { + dmap[nvals[i]] = true; + } + for (size_t i = 0; i < ovals.size(); i++) { + if (dmap.find(ovals[i]) == dmap.end()) { + // in active but not in working + dvals.push_back(ovals[i]); + } + } +} + /* this is the equivalent of the listNodeStatus() from the original * perl API. it provides the "status" ("deleted", "added", "changed", * or "static") of each child node of specified path. diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp index caf845b..492462e 100644 --- a/src/cstore/cstore.hpp +++ b/src/cstore/cstore.hpp @@ -183,6 +183,8 @@ public: bool cfgPathChanged(const vector& path_comps); void cfgPathGetDeletedChildNodes(const vector& path_comps, vector& cnodes); + void cfgPathGetDeletedValues(const vector& path_comps, + vector& dvals); void cfgPathGetChildNodesStatus(const vector& path_comps, map& cmap); -- cgit v1.2.3