/*
* 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
// for debian's version comparison algorithm
#define APT_COMPATIBILITY 986
#include
#include
#include
#include
////// 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 = "/tmp/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;
Cstore::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 + "=/;");
}
////// 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,
Cstore::MapT& 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);
sort_nodes(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)
{
ASSERT_IN_SESSION;
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. also mark "changed".
*/
bool ret = (write_value(def.def_default) && mark_display_default()
&& unmark_deactivated() && mark_changed_with_ancestors());
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();
}
}
if (ret) {
// mark changed
ret = mark_changed_with_ancestors();
}
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)
{
ASSERT_IN_SESSION;
// 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)
{
ASSERT_IN_SESSION;
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)
{
ASSERT_IN_SESSION;
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)
{
ASSERT_IN_SESSION;
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)
{
ASSERT_IN_SESSION;
vtw_def def;
string terr;
if (!get_parsed_tmpl(path_comps, false, def, terr)) {
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.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;
}
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);
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)
{
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;
}
/* 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)
{
ASSERT_IN_SESSION;
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)
{
ASSERT_IN_SESSION;
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 == "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 (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
break;
}
sort(comp_vals.begin(), comp_vals.end());
for (size_t i = 0; i < comp_vals.size(); i++) {
pair hpair(comp_vals[i], "");
push_tmpl_path(hpair.first);
vtw_def cdef;
if (tmpl_parse(cdef) && cdef.def_node_help) {
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 (size_t 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 {
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);
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.
size_t 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 (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 + "); ");
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)
{
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 vector& 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 vector& 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 vector& args)
{
ASSERT_IN_SESSION;
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)
{
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.
*/
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)
{
ASSERT_IN_SESSION;
string otagnode = args[0];
string otagval = args[1];
string ntagval = args[4];
push_cfg_path(otagnode);
/* also mark changed. note that it's marking the "tag node" but not the
* "tag values" since one is being "deleted" and the other is being
* "added" anyway.
*/
bool ret = (rename_child_node(otagval, ntagval)
&& mark_changed_with_ancestors());
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)
{
ASSERT_IN_SESSION;
string otagnode = args[0];
string otagval = args[1];
string 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 vector& args, const vtw_def& def)
{
ASSERT_IN_SESSION;
// 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;
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 vector& args)
{
ASSERT_IN_SESSION;
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)
{
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 vector& 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 vector& 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 vector& 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 vector& path_comps)
{
ASSERT_IN_SESSION;
if (cfgPathDeleted(path_comps) || cfgPathAdded(path_comps)) {
return true;
}
SAVE_PATHS;
append_cfg_path(path_comps);
bool ret = cfg_node_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)
{
ASSERT_IN_SESSION;
cfgPathGetDeletedChildNodesDA(path_comps, cnodes, false);
}
// same as above but "deactivate-aware"
void
Cstore::cfgPathGetDeletedChildNodesDA(const vector& 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 vector& path_comps,
vector& dvals)
{
ASSERT_IN_SESSION;
cfgPathGetDeletedValuesDA(path_comps, dvals, false);
}
// same as above but DA
void
Cstore::cfgPathGetDeletedValuesDA(const vector& 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 vector& path_comps, bool active_cfg)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
vector ppath;
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
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)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
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)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
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 (!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;
}
SAVE_PATHS;
append_cfg_path(path_comps);
get_all_child_node_names(cnodes, active_cfg, include_deactivated);
RESTORE_PATHS;
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 vector& 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 vector& path_comps, string& value,
bool active_cfg, bool include_deactivated)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
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)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
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)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
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)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
SAVE_PATHS;
append_cfg_path(path_comps);
bool ret = get_comment(comment, active_cfg);
RESTORE_PATHS;
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)
{
if (!active_cfg) {
ASSERT_IN_SESSION;
}
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.).
* 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 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
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
vector ppath = path_comps;
MapT::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();
}
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 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
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
vector ppath = path_comps;
MapT::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/@").
* 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 string& ref_str, vtw_type_e& type, bool from_active)
{
char *ret = NULL;
SAVE_PATHS;
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.
ret = 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)
{
ASSERT_IN_SESSION;
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);
// note: also mark changed
bool ret = (mark_deactivated() && unmark_deactivated_descendants()
&& mark_changed_with_ancestors());
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)
{
ASSERT_IN_SESSION;
SAVE_PATHS;
append_cfg_path(path_comps);
// note: also mark changed
bool ret = (unmark_deactivated() && mark_changed_with_ancestors());
RESTORE_PATHS;
return ret;
}
/* "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 vector& path_comps)
{
ASSERT_IN_SESSION;
SAVE_PATHS;
append_cfg_path(path_comps);
bool ret = unmark_changed_with_descendants();
RESTORE_PATHS;
return ret;
}
////// 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 {
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);
va_end(alist);
if (fout) {
fclose(fout);
// fdout is implicitly closed
} else if (fdout >= 0) {
close(fdout);
}
}
////// private functions
bool
Cstore::sort_func_deb_version(string a, string b)
{
return (pkgVersionCompare(a, b) < 0);
}
void
Cstore::sort_nodes(vector& nvec, unsigned int sort_alg)
{
if (_sort_func_map.find(sort_alg) == _sort_func_map.end()) {
return;
}
sort(nvec.begin(), nvec.end(), _sort_func_map[sort_alg]);
}
/* 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 (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.
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 (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;
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;
}
{
/* 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.tag && def.def_type != ERROR_TYPE) {
output_user("Cannot %s a leaf configuration node\n", op.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
*/
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_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;
}
/* 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 (size_t 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 (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");
}
// 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;
}
}
}
if (!mark_changed_with_ancestors()) {
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)) {
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();
}
}
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;
}
/* 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 vector& path_comps,
Cstore::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
vector ppath = path_comps;
MapT::iterator it = umap.begin();
for (; it != umap.end(); ++it) {
string c = (*it).first;
ppath.push_back(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_back();
}
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 vector& path_comps,
Cstore::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);
vector ppath = path_comps;
for (size_t i = 0; i < work_nodes.size(); i++) {
ppath.push_back(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 {
SAVE_PATHS;
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;
}
RESTORE_PATHS;
}
ppath.pop_back();
}
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();
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
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. 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 (size_t 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 (size_t 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 (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;
}
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;
}
}