/* * 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 #include #include #include #include #include #include #include #include #include #include #include namespace cstore { // begin namespace cstore using namespace cnode; ////// 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/files const string Cstore::C_ENUM_SCRIPT_DIR = "/opt/vyatta/share/enumeration"; const string Cstore::C_LOGFILE_STDOUT = "/var/log/vyatta/cfg-stdout.log"; //// sorting const unsigned int Cstore::SORT_DEFAULT = 0; const unsigned int Cstore::SORT_DEB_VERSION = 0; const unsigned int Cstore::SORT_NONE = 1; ////// static bool Cstore::_init = false; MapT Cstore::_sort_func_map; ////// 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) { init(); string decl = "declare -x "; env = (decl + C_ENV_EDIT_LEVEL + "=/; "); env += (decl + C_ENV_TMPL_LEVEL + "=/;"); } ////// factory functions // for "current session" (see UnionfsCstore constructor for details) Cstore * Cstore::createCstore(bool use_edit_level) { return (new unionfs::UnionfsCstore(use_edit_level)); } // utility function bool Cstore::contains_whitespace(const char *name) { int i = 0; char c; while (name[i]) { c = name[i]; if (isspace(c)) return true; i++; } return false; } // end utility function // for "specific session" (see UnionfsCstore constructor for details) Cstore * Cstore::createCstore(const string& session_id, string& env) { return (new unionfs::UnionfsCstore(session_id, env)); } ////// 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 Cpath& path_comps, bool validate_vals) { // if we can get parsed tmpl, path is valid tr1::shared_ptr def(get_parsed_tmpl(path_comps, validate_vals)); return (def.get() != 0); } /* same as above but return parsed template. return 0 if invalid/failed. * 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. */ tr1::shared_ptr Cstore::parseTmpl(const Cpath& path_comps, bool validate_vals) { return get_parsed_tmpl(path_comps, validate_vals); } /* 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 Cpath& path_comps, MapT& tmap, bool allow_val) { /* 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. */ tr1::shared_ptr def(get_parsed_tmpl(path_comps, false)); if (!def.get()) { return false; } if (!allow_val && def->isValue()) { /* 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->isValue()) { tmap["is_value"] = "1"; } // make the map if (!def->isTypeless(1)) { tmap["type"] = def->getTypeName(1); } if (!def->isTypeless(2)) { tmap["type2"] = def->getTypeName(2); } if (def->getNodeHelp()) { tmap["help"] = def->getNodeHelp(); } if (def->isMulti()) { tmap["multi"] = "1"; if (def->getMultiLimit() > 0) { ostringstream s; s << def->getMultiLimit(); tmap["limit"] = s.str(); } } else if (def->isTag()) { tmap["tag"] = "1"; if (def->getTagLimit() > 0) { ostringstream s; s << def->getTagLimit(); tmap["limit"] = s.str(); } } else if (def->getDefault()) { tmap["default"] = def->getDefault(); } if (def->getEnumeration()) { tmap["enum"] = def->getEnumeration(); } if (def->getAllowed()) { tmap["allowed"] = def->getAllowed(); } if (def->getValHelp()) { tmap["val_help"] = def->getValHelp(); } 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 Cpath& path_comps, vector& cnodes) { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_tmpl_path(path_comps); get_all_tmpl_child_node_names(cnodes); sort_nodes(cnodes); } /* delete specified "logical path" from "working config". * return true if successful. otherwise return false. */ bool Cstore::deleteCfgPath(const Cpath& path_comps) { ASSERT_IN_SESSION; string terr; tr1::shared_ptr def(get_parsed_tmpl(path_comps, false, terr)); if (!def.get()) { output_user("%s\n", terr.c_str()); return false; } if (!cfg_path_exists(path_comps, false, true)) { output_user("Nothing to delete (the specified %s does not exist)\n", (!def->isValue() || def->isTag()) ? "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->getDefault()) { // case 1. construct path for value file. #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); if (def->isValue()) { // 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. also mark "changed". */ if (!(write_value(def->getDefault()) && mark_display_default() && unmark_deactivated() && mark_changed_with_ancestors())) { output_user("Failed to set default value during delete\n"); return false; } return true; } /* 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; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); if (!def->isValue()) { // sub-case (2) ret = remove_node(); } else { // last comp is value if (def->isTag()) { // sub-case (1c) ret = remove_tag(); } else if (def->isMulti()) { // 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(); } } if (ret) { // mark changed ret = mark_changed_with_ancestors(); } 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 Cpath& path_comps) { ASSERT_IN_SESSION; Cpath *pcomps = const_cast(&path_comps); // if we can get parsed tmpl, path is valid string terr; tr1::shared_ptr def(get_parsed_tmpl(path_comps, true, terr)); if (!def.get()) { output_user("%s\n", terr.c_str()); return false; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif if (def->isTag() && contains_whitespace((*pcomps)[pcomps->size() - 1])) { string output = "Tag node value name must not contain whitespace\n"; output_user(output.c_str()); return false; } if (!def->isValue()) { if (!def->isTypeless()) { /* 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. */ string output = "Configuration path: ["+path_comps.to_string()+"] requires a value\n"; output_user(output.c_str()); return 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, "")) { 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 Cpath& path_comps) { ASSERT_IN_SESSION; tr1::shared_ptr def(validate_act_deact(path_comps, "activate")); if (!def.get()) { 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; } if (def->isTagValue() && def->getTagLimit() > 0) { // we are activating a tag, and there is a limit on number of tags. vector cnodes; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); string t; pop_cfg_path(t); // get child nodes, excluding deactivated ones. get_all_child_node_names(cnodes, false, false); if (def->getTagLimit() <= cnodes.size()) { // limit exceeded output_user("Cannot activate \"%s\": number of values exceeds limit " "(%d allowed)\n", t.c_str(), def->getTagLimit()); return false; } } return true; } /* check if specified "logical path" is valid for "deactivate" operation * return true if valid. otherwise return false. */ bool Cstore::validateDeactivatePath(const Cpath& path_comps) { ASSERT_IN_SESSION; tr1::shared_ptr def(validate_act_deact(path_comps, "deactivate")); return (def.get() != 0); } /* 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 Cpath& path_comps, string& env) { ASSERT_IN_SESSION; string terr; tr1::shared_ptr def(get_parsed_tmpl(path_comps, false, terr)); if (!def.get()) { output_user("%s\n", terr.c_str()); return false; } /* "edit" is only allowed when path ends at a * (1) "tag value" * OR * (2) "typeless node" */ if (!def->isTagValue() && !def->isTypeless()) { // neither "tag value" nor "typeless node" output_user("The \"edit\" command cannot be issued " "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)) { string output = "Configuration path: ["+path_comps.to_string()+"] is not valid\n"; output_user(output.c_str()); return false; } if (!set_cfg_path(path_comps, false)) { output_user("Failed to create the specified config path\n"); return false; } } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); append_tmpl_path(path_comps); get_edit_env(env); /* 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) { ASSERT_IN_SESSION; /* "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; } string terr; Cpath path_comps; tr1::shared_ptr def(get_parsed_tmpl(path_comps, false, terr)); if (!def.get()) { // this should not happen since it's using existing levels output_user("%s\n", terr.c_str()); return false; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif if (def->isTagValue()) { // 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); // 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) { ASSERT_IN_SESSION; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif while (!edit_level_at_root()) { pop_cfg_path(); pop_tmpl_path(); } get_edit_env(env); // also see getEditEnv for comment on save/restore above return true; } void Cstore::getAllowedVarRef(string& astr){ size_t varloc = 0; size_t strloc = 0; vtw_type_e vtype = ERROR_TYPE; string exe_string = ""; string varref = ""; size_t first_paren, second_paren; while(true) { varloc = astr.find("$VAR", strloc); if (varloc == string::npos){ varloc = astr.size(); exe_string += astr.substr(strloc, (varloc-strloc+1)); break; } exe_string += astr.substr(strloc, (varloc-strloc)); strloc = varloc; first_paren = astr.find('(', strloc); second_paren = astr.find(')', strloc); varref = astr.substr((first_paren+1), (second_paren-first_paren-1)); varref = string(getVarRef(varref.c_str(), vtype, false)); strloc = second_paren+1; exe_string += varref; } astr = exe_string; return; } /* 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 Cpath& comps, string& env) { ASSERT_IN_SESSION; string cmd = comps[0]; string last_comp = comps.back(); Cpath pcomps; for (size_t i = 1; i < (comps.size() - 1); i++) { pcomps.push(comps[i]); } bool exists_only = (cmd == "delete" || cmd == "show" || cmd == "comment" || cmd == "activate" || cmd == "deactivate"); /* at this point, pcomps contains the command line arguments minus the * "command" and the last one. */ #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif bool is_typeless = true; bool is_leaf_value = false; bool is_value = false; tr1::shared_ptr def; if (pcomps.size() > 0) { def = get_parsed_tmpl(pcomps, false); if (!def.get()) { // invalid path return false; } if (exists_only && !cfg_path_exists(pcomps, false, true)) { // invalid path for the command (must exist) return false; } append_cfg_path(pcomps); append_tmpl_path(pcomps); is_typeless = def->isTypeless(); is_leaf_value = def->isLeafValue(); is_value = def->isValue(); } else { /* we are at root. default values simulate a typeless node so nop. * note that in this case def is "empty", so must ensure that it's * not used. */ } /* at this point, cfg and tmpl paths are constructed up to the comp * before last_comp, and def is parsed. */ if (is_leaf_value) { // invalid path (this means the comp before last_comp is a leaf value) return false; } vector comp_vals; string comp_string; string comp_help; vector > help_pairs; bool last_comp_val = true; if (is_typeless || 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. * * also, the "root" node case above will reach this block, so * must not use def in this block. */ 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 (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]); } } if (comp_vals.size() == 0) { // no matches return false; } sort(comp_vals.begin(), comp_vals.end()); /* loop below calls get_parsed_tmpl(), which takes the whole path. * so need to save current paths and reset them before (and restore them * after). */ #if __GNUC__ < 6 auto_ptr save1(create_save_paths()); #else unique_ptr save1(create_save_paths()); #endif reset_paths(); for (size_t i = 0; i < comp_vals.size(); i++) { pair hpair(comp_vals[i], ""); pcomps.push(comp_vals[i]); tr1::shared_ptr cdef(get_parsed_tmpl(pcomps, false)); if (cdef.get() && cdef->getNodeHelp()) { hpair.second = cdef->getNodeHelp(); } else { hpair.second = ""; } help_pairs.push_back(hpair); pcomps.pop(); } // 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 * * also, cannot be "root" node if we reach here, so def can be used. */ // first, handle completions. if (def->isTag()) { // 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 (!exists_only && (def->getEnumeration() || def->getAllowed())) { /* 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 (size_t i = 0; i < comps.size(); i++) { cmd_str += " '"; cmd_str += comps[i]; cmd_str += "'"; } cmd_str += "); "; if (def->getEnumeration()) { cmd_str += (C_ENUM_SCRIPT_DIR + "/" + def->getEnumeration()); } else { string astr = def->getAllowed(); shell_escape_squotes(astr); getAllowedVarRef(astr); cmd_str += "_cstore_internal_allowed () { eval '"; cmd_str += astr; cmd_str += "'; }; _cstore_internal_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 (!exists_only && def->getActions(syntax_act)) { // look for "self ref in values" from syntax const valstruct *vals = get_syntax_self_in_valstruct(def->getActions(syntax_act)); if (vals) { if (vals->cnt == 0 && vals->val) { comp_vals.push_back(vals->val); } 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->getCompHelp()) { // "comp_help" exists. comp_help = def->getCompHelp(); shell_escape_squotes(comp_help); } if (def->getValHelp()) { // has val_help. first separate individual lines. size_t start = 0, i = 0; vector vhelps; for (i = 0; (def->getValHelp())[i]; i++) { if ((def->getValHelp())[i] == '\n') { vhelps.push_back(string(&((def->getValHelp())[start]), i - start)); start = i + 1; } } if (start < i) { vhelps.push_back(string(&((def->getValHelp())[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->isTypeless(1)) { // first val_help. pair with "type". help_pairs.push_back(pair( def->getTypeName(1), vhelps[i])); } if (i == 1 && !def->isTypeless(2)) { // second val_help. pair with second "type". help_pairs.push_back(pair( def->getTypeName(2), vhelps[i])); } } else { // ';' at index sc help_pairs.push_back(pair( vhelps[i].substr(0, sc), vhelps[i].substr(sc + 1))); } } } else if (!def->isTypeless(1) && def->getNodeHelp()) { // simple case. just use "type" and "help" help_pairs.push_back(pair(def->getTypeName(1), def->getNodeHelp())); } } /* from this point on cannot use def (since the "root" node case * can reach here). */ // this var is the array of possible completions env = (C_ENV_SHAPI_COMP_VALS + "=("); for (size_t 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 (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); 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 + "); "); return true; } /* 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 Cpath& path_comps) { ASSERT_IN_SESSION; return set_cfg_path(path_comps, true); } /* check if specified "arguments" is valid for "rename" operation * return true if valid. otherwise return false. */ bool Cstore::validateRenameArgs(const Cpath& args) { ASSERT_IN_SESSION; 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 Cpath& args) { ASSERT_IN_SESSION; 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 Cpath& args) { ASSERT_IN_SESSION; Cpath epath; Cpath nargs; if (!conv_move_args_for_rename(args, epath, nargs)) { output_user("Invalid move command\n"); return false; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(epath); append_tmpl_path(epath); return validate_rename_copy(nargs, "move"); } /* 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 Cpath& args) { ASSERT_IN_SESSION; const char *otagnode = args[0]; const char *otagval = args[1]; const char *ntagval = args[4]; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif push_cfg_path(otagnode); if (!rename_child_node(otagval, ntagval)) { return false; } /* also mark the new "tag value" changed since one possible scenario is that * the "new" tag value was there before but is being deleted, and something * else is being renamed to the same tag value. one side effect of this is * that if the subtree under the new tag value is completely identical * before and after this delete/rename sequence, then it will be marked * "changed" even though nothing changed. */ push_cfg_path(ntagval); return mark_changed_with_ancestors(); } /* 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 Cpath& args) { ASSERT_IN_SESSION; const char *otagnode = args[0]; const char *otagval = args[1]; const char *ntagval = args[4]; push_cfg_path(otagnode); /* also mark changed. note that it's marking the "tag node" but not the * new "tag value" since it is being "added" anyway. */ bool ret = (copy_child_node(otagval, ntagval) && mark_changed_with_ancestors()); 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 Cpath& args) { ASSERT_IN_SESSION; /* separate path from comment. * follow the original implementation: the last arg is the comment, and * everything else is part of the path. */ Cpath path_comps(args); string comment; path_comps.pop(comment); // check the path string terr; tr1::shared_ptr def(get_parsed_tmpl(path_comps, false, terr)); if (!def.get()) { 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->isLeafValue()) { /* 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->isTagNode()) { /* 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; } if (comment.find("CONFIGURATION COMMENTED OUT DURING MIGRATION BELOW") != string::npos){ // Don't allow users to set configuration migration comments return false; } if (comment.find("CONFIGURATION COMMENTED OUT DURING MIGRATION ABOVE") != string::npos){ // Don't allow users to set configuration migration comments return false; } bool ret = false; { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); 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"); } } } if (ret) { // mark the root as changed for "comment" ret = mark_changed_with_ancestors(); } return ret; } /* discard all changes in working config. * return true if successful. otherwise return false. */ bool Cstore::discardChanges() { ASSERT_IN_SESSION; // 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 Cpath& args) { ASSERT_IN_SESSION; Cpath epath; Cpath nargs; if (!conv_move_args_for_rename(args, epath, nargs)) { output_user("Invalid move command\n"); return false; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(epath); append_tmpl_path(epath); return renameCfgPath(nargs); } /* 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 Cpath& path_comps, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } return cfg_path_exists(path_comps, active_cfg, false); } // same as above but "deactivate-aware" bool Cstore::cfgPathExistsDA(const Cpath& path_comps, bool active_cfg, bool include_deactivated) { if (!active_cfg) { ASSERT_IN_SESSION; } 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 Cpath& path_comps) { ASSERT_IN_SESSION; // 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 Cpath& path_comps) { ASSERT_IN_SESSION; // 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) cfg_node_changed() */ bool Cstore::cfgPathChanged(const Cpath& path_comps) { ASSERT_IN_SESSION; if (cfgPathDeleted(path_comps) || cfgPathAdded(path_comps)) { return true; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return cfg_node_changed(); } /* get names of "deleted" child nodes of specified path during commit * operation. names are returned in cnodes. */ void Cstore::cfgPathGetDeletedChildNodes(const Cpath& path_comps, vector& cnodes) { ASSERT_IN_SESSION; cfgPathGetDeletedChildNodesDA(path_comps, cnodes, false); } // same as above but "deactivate-aware" void Cstore::cfgPathGetDeletedChildNodesDA(const Cpath& path_comps, vector& cnodes, bool include_deactivated) { ASSERT_IN_SESSION; vector acnodes; cfgPathGetChildNodesDA(path_comps, acnodes, true, include_deactivated); vector wcnodes; cfgPathGetChildNodesDA(path_comps, wcnodes, false, include_deactivated); MapT cmap; for (size_t i = 0; i < wcnodes.size(); i++) { cmap[wcnodes[i]] = true; } 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]); } } sort_nodes(cnodes); } /* 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 Cpath& path_comps, vector& dvals) { ASSERT_IN_SESSION; cfgPathGetDeletedValuesDA(path_comps, dvals, false); } // same as above but DA void Cstore::cfgPathGetDeletedValuesDA(const Cpath& path_comps, vector& dvals, bool include_deactivated) { ASSERT_IN_SESSION; vector ovals; vector nvals; if (!cfgPathGetValuesDA(path_comps, ovals, true, include_deactivated) || !cfgPathGetValuesDA(path_comps, nvals, false, include_deactivated)) { return; } MapT 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]); } } } /* 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 Cpath& path_comps, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } Cpath ppath; for (size_t i = 0; i < path_comps.size(); i++) { ppath.push(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 Cpath& path_comps, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return marked_deactivated(active_cfg); } /* get names of child nodes of specified path in working config or active * config. names are returned in cnodes. */ void Cstore::cfgPathGetChildNodes(const Cpath& path_comps, vector& cnodes, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } cfgPathGetChildNodesDA(path_comps, cnodes, active_cfg, false); } // same as above but "deactivate-aware" void Cstore::cfgPathGetChildNodesDA(const Cpath& path_comps, vector& cnodes, bool active_cfg, bool include_deactivated) { if (!active_cfg) { ASSERT_IN_SESSION; } 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; } { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); get_all_child_node_names(cnodes, active_cfg, include_deactivated); } sort_nodes(cnodes); } /* 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 Cpath& path_comps, string& value, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } return cfgPathGetValueDA(path_comps, value, active_cfg, false); } // same as above but "deactivate-aware" bool Cstore::cfgPathGetValueDA(const Cpath& path_comps, string& value, bool active_cfg, bool include_deactivated) { if (!active_cfg) { ASSERT_IN_SESSION; } tr1::shared_ptr def(get_parsed_tmpl(path_comps, false)); if (!def.get()) { // 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->isSingleLeafNode()) { // 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; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); 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]; return true; } } return false; } /* 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 Cpath& path_comps, vector& values, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } return cfgPathGetValuesDA(path_comps, values, active_cfg, false); } // same as above but "deactivate-aware" bool Cstore::cfgPathGetValuesDA(const Cpath& path_comps, vector& values, bool active_cfg, bool include_deactivated) { if (!active_cfg) { ASSERT_IN_SESSION; } tr1::shared_ptr def(get_parsed_tmpl(path_comps, false)); if (!def.get()) { // 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->isMultiLeafNode()) { // 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; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return read_value_vec(values, active_cfg); } /* 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 Cpath& path_comps, string& comment, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return get_comment(comment, active_cfg); } /* 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 Cpath& path_comps, bool active_cfg) { if (!active_cfg) { ASSERT_IN_SESSION; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return marked_display_default(active_cfg); } /* 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 confusion, 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 Cpath& path_comps) { tr1::shared_ptr def(get_parsed_tmpl(path_comps, false)); if (!def.get()) { // 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); return commit::isCommitPathEffective(*this, path_comps, def, in_active, in_work); } /* 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 Cpath& 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 MapT cmap; vector acnodes; vector wcnodes; cfgPathGetChildNodes(path_comps, acnodes, true); cfgPathGetChildNodes(path_comps, wcnodes, false); for (size_t i = 0; i < acnodes.size(); i++) { cmap[acnodes[i]] = true; } for (size_t i = 0; i < wcnodes.size(); i++) { cmap[wcnodes[i]] = true; } // get only the effective ones from the union Cpath ppath(path_comps); MapT::iterator it = cmap.begin(); for (; it != cmap.end(); ++it) { string c = (*it).first; ppath.push(c); if (cfgPathEffective(ppath)) { cnodes.push_back(c); } ppath.pop(); } sort_nodes(cnodes); } /* 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 Cpath& path_comps, string& value) { if (!inSession()) { // not in a config session. use active config only. return cfgPathGetValue(path_comps, value, true); } Cpath 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(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(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 Cpath& 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 MapT vmap; vector ovals; vector nvals; cfgPathGetValues(path_comps, ovals, true); cfgPathGetValues(path_comps, nvals, false); for (size_t i = 0; i < ovals.size(); i++) { vmap[ovals[i]] = true; } for (size_t i = 0; i < nvals.size(); i++) { vmap[nvals[i]] = true; } // get only the effective ones from the union Cpath ppath(path_comps); MapT::iterator it = vmap.begin(); for (; it != vmap.end(); ++it) { string c = (*it).first; ppath.push(c); if (cfgPathEffective(ppath)) { values.push_back(c); } ppath.pop(); } return (values.size() > 0); } /* get the value string that corresponds to specified variable ref string. * ref_str: var ref string (e.g., "./cost/@"). * type: (output) the node type. * from_active: if true, value string should come from "active config". * otherwise from "working config". * return a pointer to the value string if successful (caller must free). * otherwise return NULL. */ char * Cstore::getVarRef(const char *ref_str, vtw_type_e& type, bool from_active) { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif VarRef vref(this, ref_str, from_active); string val; vtw_type_e t; if (vref.getValue(val, t)) { type = t; // follow original implementation. caller is supposed to free this. return strdup(val.c_str()); } return NULL; } /* 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 char *ref_str, const char *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). * * XXX also the behavior here follows the original implementation and * has these limitations: * * it does not check if the type of the specified value is * correct, e.g., it may write a txt value to a u32 node if * that's what the template specifies. * * it only supports only single-value leaf nodes. */ #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif VarRef vref(this, ref_str, to_active); Cpath pcomps; if (vref.getSetPath(pcomps)) { reset_paths(); tr1::shared_ptr def(get_parsed_tmpl(pcomps, false)); if (def.get() && def->isSingleLeafNode()) { // currently only support single-value node append_cfg_path(pcomps); if (write_value(value, to_active)) { return true; } } } return false; } /* 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 Cpath& path_comps) { ASSERT_IN_SESSION; if (cfgPathDeactivated(path_comps)) { output_user("The specified configuration node is already deactivated\n"); // treat as success return true; } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); // note: also mark changed return (mark_deactivated() && unmark_deactivated_descendants() && mark_changed_with_ancestors()); } /* 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 Cpath& path_comps) { ASSERT_IN_SESSION; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); // note: also mark changed return (unmark_deactivated() && mark_changed_with_ancestors()); } // load specified config file bool Cstore::loadFile(const char *filename) { if (!inSession()) { output_user("Cannot load config outside configuration session\n"); // exit handled by assert below } ASSERT_IN_SESSION; FILE *fin = fopen(filename, "r"); if (!fin) { output_user("Failed to open specified config file\n"); return false; } // get the config tree from the file CfgNode *froot = cparse::parse_file(fin, *this); fclose(fin); if (!froot) { output_user("Failed to parse specified config file\n"); return false; } // get the config tree from the active config Cpath args; CfgNode aroot(*this, args, true, true); // get the "commands diff" between the two vector del_list; vector set_list; vector com_list; get_cmds_diff(aroot, *froot, del_list, set_list, com_list); delete froot; // "apply" the changes to the working config for (size_t i = 0; i < del_list.size(); i++) { if (!deleteCfgPath(del_list[i])) { print_path_vec("Delete [", "] failed\n", del_list[i], "'"); } } for (size_t i = 0; i < set_list.size(); i++) { if (!validateSetPath(set_list[i]) || !setCfgPath(set_list[i])) { print_path_vec("Set [", "] failed\n", set_list[i], "'"); } } for (size_t i = 0; i < com_list.size(); i++) { if (!commentCfgPath(com_list[i])) { string comment = string(com_list[i][com_list[i].size()-1]); if (comment.find("CONFIGURATION COMMENTED OUT DURING MIGRATION BELOW") == string::npos && comment.find("CONFIGURATION COMMENTED OUT DURING MIGRATION ABOVE") == string::npos) { print_path_vec("Comment [", "] failed\n", com_list[i], "'"); } } } return true; } /* "changed" status handling. * the "changed" status is used during commit to check if a node has been * changed. note that if a node is "changed", all of its ancestors are also * considered changed (this follows the original logic). * * the original backend implementation only uses the "changed" marker at * "root" to indicate whether the whole config has changed. for the rest * of the config hierarchy, the original implementation treated all nodes * that are present in the unionfs "changes only" directory as "changed". * * this worked until the introduction of "deactivate". since deactivated * nodes are also present in the "changes only" directory, the backend * treat them as "changed". on the other hand, deleted nodes don't appear * in "changes only", so they are _not_ treated as "changed". this creates * problems in various parts of the backend. * * the new CLI backend/library "marks" all changed nodes explicitly, and the * "changed" status depends on such markers. the marking is done using the * pure virtual mark_changed_with_ancestors() function, which is provided * by the low-level implementation, so it does not have to be done as a * "per-node file marker" as long as the low-level implementation can * correctly answer the "changed" query for a given path. * * note that "changed" nodes does not include "added" and "deleted" nodes. * for the convenience of implementation, the backend must always query * for "changed" nodes *after* "added" and "deleted" nodes. in other * words, the backend will only treat a node as "changed" if it is neither * "added" nor "deleted". currently there are only two places that perform * changed status query: cfgPathGetChildNodesStatus() and * cfgPathGetChildNodesStatusDA(). see those two functions for the usage. * * what this means is that the backend can choose to either mark or not * mark "added"/"deleted" nodes as "changed" at its convenience. for * example, "set" and "delete" always do the marking, but "rename" and * "copy" do not. * * changed status queries are provided by the cfg_node_changed() function, * and changed markers can be removed by unmarkCfgPathChanged() below (used * by "commit"). */ /* unmark "changed" status of specified path in working config. * this is used, e.g., at the end of "commit" to reset a subtree. * note: unmarking a node means all of its descendants are also unmarked, * i.e., they become "unchanged". * return true if successful. otherwise return false. */ bool Cstore::unmarkCfgPathChanged(const Cpath& path_comps) { ASSERT_IN_SESSION; #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return unmark_changed_with_descendants(); } // execute the specified actions bool Cstore::executeTmplActions(char *at_str, const Cpath& path, const Cpath& disp_path, const vtw_node *actions, const vtw_def *def) { string sdisp = " "; sdisp += disp_path.to_string(); sdisp += " "; set_at_string(at_str); #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path); append_tmpl_path(path); var_ref_handle = (void *) this; // const_cast for legacy code bool ret = execute_list(const_cast(actions), def, sdisp.c_str()); var_ref_handle = NULL; return ret; } bool Cstore::cfgPathMarkedCommitted(const Cpath& path_comps, bool is_delete) { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return marked_committed(is_delete); } bool Cstore::markCfgPathCommitted(const Cpath& path_comps, bool is_delete) { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); return mark_committed(is_delete); } ////// protected functions Cstore::SavePaths::~SavePaths() { } void Cstore::output_user(const char *fmt, ...) { va_list alist; va_start(alist, fmt); voutput_user(out_stream, stdout, fmt, alist); va_end(alist); } void Cstore::output_user_err(const char *fmt, ...) { va_list alist; va_start(alist, fmt); voutput_user(err_stream, stderr, fmt, alist); va_end(alist); } void Cstore::output_internal(const char *fmt, ...) { va_list alist; va_start(alist, fmt); voutput_internal(fmt, alist); va_end(alist); } void Cstore::exit_internal(const char *fmt, ...) { va_list alist; va_start(alist, fmt); vexit_internal(fmt, alist); va_end(alist); } void Cstore::assert_internal(bool cond, const char *fmt, ...) { if (cond) { return; } va_list alist; va_start(alist, fmt); vexit_internal(fmt, alist); va_end(alist); } ////// private functions bool Cstore::sort_func_deb_version(string a, string b) { return debVS.CmpVersion(a, b) < 0; } void Cstore::sort_nodes(vector& nvec, unsigned int sort_alg) { MapT::iterator p = _sort_func_map.find(sort_alg); if (p == _sort_func_map.end()) { return; } sort(nvec.begin(), nvec.end(), p->second); } /* 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. * * note: if the last comp is already "node.tag", is_tag won't be set. * currently this should only happen when get_parsed_tmpl() "borrows" * comps from the current tmpl path, in which case this is not * a problem. */ bool Cstore::append_tmpl_path(const Cpath& path_comps, bool& is_tag) { for (size_t i = 0; i < path_comps.size(); i++) { is_tag = false; 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. is_tag = true; continue; } // not a valid path return false; } return true; } typedef MapT, CpathHash> TmplCacheT; static TmplCacheT _tmpl_cache; /* check whether specified "logical path" is valid template path. * then template at the path is parsed. * path_comps: path components. * validate_vals: whether to validate all "values" along specified path. * error: (output) error message if failed. * return parsed template if successful. otherwise return 0. * note: * also, if last path component is value (i.e., isValue()), the template * parsed is actually at "full path - 1". */ tr1::shared_ptr Cstore::get_parsed_tmpl(const Cpath& path_comps, bool validate_vals, string& error) { tr1::shared_ptr rtmpl; // default error message error = "Configuration path: ["+path_comps.to_string()+"] is not valid\n"; bool do_caching = false; if (tmpl_path_at_root()) { if (path_comps.size() == 0) { // empty path not valid return rtmpl; } // we are starting from root => caching applies do_caching = true; TmplCacheT::iterator p = _tmpl_cache.find(path_comps); if (p != _tmpl_cache.end()) { // return cached return p->second; } } #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif /* 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. */ Cpath *pcomps = const_cast(&path_comps); Cpath new_path_comps; size_t p_size = path_comps.size(); if (p_size < 2) { Cpath tmp; for (unsigned int i = 0; i < 2 && (i + p_size) < 2; i++) { if (!tmpl_path_at_root()) { string last; pop_tmpl_path(last); tmp.push(last); } } while (tmp.size() > 0) { new_path_comps.push(tmp.back()); tmp.pop(); } new_path_comps /= path_comps; pcomps = &new_path_comps; } 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 (size_t i = 0; i < (pcomps->size() - 1); i++) { if ((*pcomps)[i][0] == 0) { // 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(); tr1::shared_ptr ttmpl(tmpl_parse()); if (!validate_val(ttmpl, (*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) { tr1::shared_ptr ttmpl(tmpl_parse()); if (ttmpl.get()) { if (ttmpl->isTag() || ttmpl->isMulti() || !ttmpl->isTypeless()) { // case (2). last component is "value". if (validate_vals) { // validate value if (!validate_val(ttmpl, (*pcomps)[pcomps->size() - 1])) { // invalid value error = "Value validation failed"; break; } } rtmpl = ttmpl; rtmpl->setIsValue(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][0] == 0) { // 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". rtmpl.reset(tmpl_parse()); if (!rtmpl.get()) { exit_internal("get_parsed_tmpl: failed to parse tmpl [%s]\n", tmpl_path_to_str().c_str()); } rtmpl->setIsValue(false); break; } // case (3) (fall through) } while (0); if (do_caching && rtmpl.get()) { // only cache if we got a valid template _tmpl_cache[path_comps] = rtmpl; } return rtmpl; } /* check if specified "logical path" is valid for "activate" or * "deactivate" operation. * return parsed template if valid. otherwise return 0. */ tr1::shared_ptr Cstore::validate_act_deact(const Cpath& path_comps, const char *op) { string terr; tr1::shared_ptr def(get_parsed_tmpl(path_comps, false, terr)); tr1::shared_ptr none; if (!def.get()) { output_user("%s\n", terr.c_str()); return none; } { /* XXX this is a temporary workaround for bug 5708, which should be * addressed _after_ the "default value"-related issues have been * resolved (see bug for more details). once those are resolved, * this workaround should be removed and the bug fixed properly. */ if (!def->isTag() && !def->isTypeless()) { output_user("Cannot %s a leaf configuration node\n", op); return none; } } if (def->isLeafValue()) { /* 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); return none; } if (!cfg_path_exists(path_comps, false, true)) { output_user("Nothing to %s (the specified %s does not exist)\n", op, (!def->isValue() || def->isTag()) ? "node" : "value"); return none; } return def; } /* check if specified args is valid for "rename" or "copy" operation. * return true if valid. otherwise return false. */ bool Cstore::validate_rename_copy(const Cpath& args, const char *op) { if (args.size() != 5 || strcmp(args[2], "to") != 0) { output_user("Invalid %s command\n", op); return false; } const char *otagnode = args[0]; const char *otagval = args[1]; const char *ntagnode = args[3]; const char *ntagval = args[4]; if (strcmp(otagnode, ntagnode) != 0) { output_user("Cannot %s from \"%s\" to \"%s\"\n", op, otagnode, ntagnode); return false; } // check the old path Cpath ppath; ppath.push(otagnode); ppath.push(otagval); string terr; tr1::shared_ptr def(get_parsed_tmpl(ppath, false, terr)); if (!def.get()) { output_user("%s\n", terr.c_str()); return false; } if (!def->isTagValue()) { // can only rename "tagnode tagvalue" output_user("Cannot %s under \"%s\"\n", op, otagnode); return false; } if (!cfg_path_exists(ppath, false, true)) { output_user("Configuration \"%s %s\" does not exist\n", otagnode, otagval); return false; } // check the new path ppath.pop(); ppath.push(ntagval); if (cfg_path_exists(ppath, false, true)) { output_user("Configuration \"%s %s\" already exists\n", ntagnode, ntagval); return false; } def = get_parsed_tmpl(ppath, true, terr); if (!def.get()) { 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 Cpath& args, Cpath& edit_path_comps, Cpath& 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 */ size_t num_args = args.size(); if (num_args < 4) { // need at least 4 args return false; } for (size_t i = 0; i < (num_args - 4); i++) { edit_path_comps.push(args[i]); } rn_args.push(args[num_args - 4]); // vif rn_args.push(args[num_args - 3]); // 100 rn_args.push(args[num_args - 2]); // to rn_args.push(args[num_args - 4]); // vif rn_args.push(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 Cpath& path_comps, bool active_cfg, bool include_deactivated) { bool ret = false; { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(path_comps); // first check if it's a "node". 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); } } if (ret && !include_deactivated && cfgPathDeactivated(path_comps, active_cfg)) { // don't include deactivated ret = false; } 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 Cpath& path_comps, bool output) { Cpath ppath; tr1::shared_ptr def; bool ret = true; bool path_exists = true; // do the set from the top down for (size_t i = 0; i < path_comps.size(); i++) { // partial path ppath.push(path_comps[i]); // get template at this level def = get_parsed_tmpl(ppath, false); if (!def.get()) { output_internal("paths[%s,%s]\n", cfg_path_to_str().c_str(), tmpl_path_to_str().c_str()); for (size_t i = 0; i < ppath.size(); i++) { output_internal(" [%s]\n", ppath[i]); } 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; } // paths have not been changed up to this point. now save them. #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif path_exists = false; // this level not in working. set it. append_cfg_path(ppath); append_tmpl_path(ppath); if (!def->isValue()) { // this level is a "node" if (!add_node()) { ret = false; break; } if (!def->isTag() && !create_default_children(ppath)) { // failed to create default child nodes for a typeless node ret = false; break; } } else if (def->isTag()) { // this level is a "tag value". // add the tag, taking the max tag limit into consideration. if (!add_tag(def->getTagLimit()) || !create_default_children(ppath)) { 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->isMulti()) { // value of multi-value node. // add the value, taking the max multi limit into consideration. if (!add_value_to_multi(def->getMultiLimit(), ppath.back())) { ret = false; break; } } else { // value of single-value node if (!write_value(ppath.back())) { ret = false; break; } } } if (!mark_changed_with_ancestors()) { ret = false; break; } } if (ret && def->isValue() && def->getDefault()) { #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif /* 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)) { if ((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; // also mark changed ret = mark_changed_with_ancestors(); } } } if (path_exists) { // whole path already exists if (output) { string userout = "Configuration path: ["+path_comps.to_string()+"] already exists\n"; output_user(userout.c_str()); } // treat as success } return ret; } /* 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. * sorted_keys: (output) contains sorted keys. call with NULL if not needed. * * note: this function is NOT "deactivate-aware". */ void Cstore::get_child_nodes_status(const Cpath& path_comps, MapT& cmap, vector *sorted_keys) { // get a union of active and working MapT umap; vector acnodes; vector wcnodes; cfgPathGetChildNodes(path_comps, acnodes, true); cfgPathGetChildNodes(path_comps, wcnodes, false); for (size_t i = 0; i < acnodes.size(); i++) { umap[acnodes[i]] = true; } for (size_t i = 0; i < wcnodes.size(); i++) { umap[wcnodes[i]] = true; } // get the status of each one Cpath ppath(path_comps); MapT::iterator it = umap.begin(); for (; it != umap.end(); ++it) { string c = (*it).first; ppath.push(c); if (sorted_keys) { sorted_keys->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(); } if (sorted_keys) { sort_nodes(*sorted_keys); } } /* DA version of the above function. * cmap: (output) contains the status of child nodes. * sorted_keys: (output) contains sorted keys. call with NULL if not needed. * * note: this follows the original perl API listNodeStatus() implementation. */ void Cstore::get_child_nodes_status_da(const Cpath& path_comps, MapT& cmap, vector *sorted_keys) { // process deleted nodes first vector del_nodes; cfgPathGetDeletedChildNodesDA(path_comps, del_nodes); for (size_t i = 0; i < del_nodes.size(); i++) { if (sorted_keys) { sorted_keys->push_back(del_nodes[i]); } cmap[del_nodes[i]] = C_NODE_STATUS_DELETED; } // get all nodes in working config vector work_nodes; cfgPathGetChildNodesDA(path_comps, work_nodes, false); Cpath ppath(path_comps); for (size_t i = 0; i < work_nodes.size(); i++) { ppath.push(work_nodes[i]); if (sorted_keys) { sorted_keys->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. * * for "changed" state, can't use cfgPathChanged() 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 __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif append_cfg_path(ppath); if (cfg_node_changed()) { cmap[work_nodes[i]] = C_NODE_STATUS_CHANGED; } else { cmap[work_nodes[i]] = C_NODE_STATUS_STATIC; } } ppath.pop(); } if (sorted_keys) { sort_nodes(*sorted_keys); } } /* 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(c); 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.c_str()); 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 size_t bc = vvec.size(); vector nvec(vvec.begin(), remove(vvec.begin(), vvec.end(), value)); size_t 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. * 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 tr1::shared_ptr& def, const char *value) { if (!def.get()) { exit_internal("validate_val: no tmpl [%s]\n", tmpl_path_to_str().c_str()); } // validate_value() may change "value". make a copy first. #if __GNUC__ < 6 auto_ptr vbuf(strdup(value)); #else unique_ptr vbuf { strdup(value), &std::free }; #endif /* set the handle to be used during validate_value() for var ref * processing. this is a global var in cli_new.c. */ var_ref_handle = (void *) this; bool ret = validate_value(def->getDef(), vbuf.get()); var_ref_handle = NULL; return ret; } /* 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(unsigned int tlimit) { string t; pop_cfg_path(t); vector cnodes; // get child nodes, excluding deactivated ones. get_all_child_node_names(cnodes, false, false); bool ret = false; do { if (tlimit > 0 && tlimit <= cnodes.size()) { // limit exceeded output_user("Cannot set node \"%s\": number of values exceeds limit" "(%d allowed)\n", t.c_str(), tlimit); break; } /* XXX the original implementation contains special case where the * previous tag should be replaced. this is probably unnecessary since * "rename" can be used for tag node anyway. also the implementation * used -1 as the limit for the special case, which can't work since * the limit is unsigned. ignore the special case for now. */ // neither of the above. just add the tag. ret = add_child_node(t); } while (0); push_cfg_path(t.c_str()); 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(unsigned int mlimit, 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 (mlimit >= 1 && vvec.size() >= mlimit) { // limit exceeded output_user("Cannot set value \"%s\": number of values exceeded " "(%d allowed)\n", value.c_str(), mlimit); 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 (size_t i = 0; i < nodes.size(); i++) { if (!include_deactivated) { push_cfg_path(nodes[i].c_str()); 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 * path_comps: path components. MUST match the work path and is only * needed for the get_parsed_tmpl() call. * 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(const Cpath& path_comps) { vector tcnodes; get_all_tmpl_child_node_names(tcnodes); bool ret = true; Cpath pcomps(path_comps); // need to save/reset/restore paths for get_parsed_tmpl() #if __GNUC__ < 6 auto_ptr save(create_save_paths()); #else unique_ptr save(create_save_paths()); #endif reset_paths(); for (size_t i = 0; i < tcnodes.size(); i++) { pcomps.push(tcnodes[i]); tr1::shared_ptr def(get_parsed_tmpl(pcomps, false)); if (def.get() && def->getDefault()) { // has default value. set it. append_cfg_path(pcomps); if (!add_node() || !write_value(def->getDefault()) || !mark_display_default()) { ret = false; } reset_paths(); } pcomps.pop(); 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) { Cpath lvec; get_edit_level(lvec); string lvl; for (size_t 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; } // show VRF and Network Namespace name on console return ("[edit" + lvl + "]\\n\\u@\\H${VRF:+(vrf:$VRF)}${NETNS:+(ns:$NETNS)}# "); } // 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; } } // print a vector of strings void Cstore::print_path_vec(const char *pre, const char *post, const Cpath& pvec, const char *quote) { output_user("%s", pre); for (size_t i = 0; i < pvec.size(); i++) { if (i > 0) { output_user(" "); } output_user("%s%s%s", quote, pvec[i], quote); } output_user("%s", post); } void Cstore::voutput_user(FILE *out, FILE *dout, const char *fmt, va_list alist) { if (out) { vfprintf(out, fmt, alist); } else if (dout) { vfprintf(dout, fmt, alist); } else { vprintf(fmt, alist); } } void Cstore::voutput_internal(const char *fmt, va_list alist) { int fdout = -1; FILE *fout = NULL; do { if ((fdout = open(C_LOGFILE_STDOUT.c_str(), 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); if (fout) { fclose(fout); // fdout is implicitly closed } else if (fdout >= 0) { close(fdout); } } void Cstore::vexit_internal(const char *fmt, va_list alist) { char buf[256]; vsnprintf(buf, 256, fmt, alist); output_internal("%s\n", buf); fprintf(stderr, "DEBUG vexit_internal: %s\n", buf); // DEBUG // output error message and exit output_user_err("%s\n", buf); exit(1); } } // end namespace cstore