summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAn-Cheng Huang <ancheng@vyatta.com>2010-07-28 14:30:32 -0700
committerAn-Cheng Huang <ancheng@vyatta.com>2010-07-28 14:30:32 -0700
commit639c835bc2730a4fbffd915f5b2028a68375ee7a (patch)
tree203d61e1d5e8ef422d6aba3851d2f83a1f838b6b /src
parent0247864ef578ac05bbac8dc5175e674ce7b82714 (diff)
downloadvyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.tar.gz
vyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.zip
add new cstore library
Diffstat (limited to 'src')
-rw-r--r--src/cli_bin.cpp237
-rw-r--r--src/cli_def.l7
-rw-r--r--src/cli_new.c343
-rw-r--r--src/cli_parse.y47
-rw-r--r--src/cli_shell_api.cpp316
-rw-r--r--src/cli_val.h19
-rw-r--r--src/cli_val_engine.c112
-rw-r--r--src/cli_val_engine.h7
-rw-r--r--src/cstore/cstore-c.cpp159
-rw-r--r--src/cstore/cstore-c.h55
-rw-r--r--src/cstore/cstore-varref.cpp288
-rw-r--r--src/cstore/cstore-varref.hpp48
-rw-r--r--src/cstore/cstore.cpp2496
-rw-r--r--src/cstore/cstore.hpp409
-rw-r--r--src/cstore/unionfs/cstore-unionfs.cpp1078
-rw-r--r--src/cstore/unionfs/cstore-unionfs.hpp217
-rw-r--r--src/delete.c391
-rw-r--r--src/set.c498
18 files changed, 5661 insertions, 1066 deletions
diff --git a/src/cli_bin.cpp b/src/cli_bin.cpp
new file mode 100644
index 0000000..420d19c
--- /dev/null
+++ b/src/cli_bin.cpp
@@ -0,0 +1,237 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <vector>
+#include <string>
+#include <libgen.h>
+
+extern "C" {
+#include <cli_val.h>
+}
+
+#include <cstore/unionfs/cstore-unionfs.hpp>
+
+static int op_idx = -1;
+static const char *op_bin_name[] = {
+ "my_set",
+ "my_delete",
+ "my_activate",
+ "my_deactivate",
+ "my_rename",
+ "my_copy",
+ "my_comment",
+ "my_discard",
+ "my_move",
+ NULL
+};
+static const char *op_Str[] = {
+ "Set",
+ "Delete",
+ "Activate",
+ "Deactivate",
+ "Rename",
+ "Copy",
+ "Comment",
+ "Discard",
+ "Move",
+ NULL
+};
+static const char *op_str[] = {
+ "set",
+ "delete",
+ "activate",
+ "deactivate",
+ "rename",
+ "copy",
+ "comment",
+ "discard",
+ "move",
+ NULL
+};
+static const bool op_need_cfg_node_args[] = {
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ false,
+ true,
+ false // dummy
+};
+#define OP_Str op_Str[op_idx]
+#define OP_str op_str[op_idx]
+#define OP_need_cfg_node_args op_need_cfg_node_args[op_idx]
+
+static void
+doSet(Cstore& cstore, const vector<string>& path_comps)
+{
+ if (!cstore.validateSetPath(path_comps)) {
+ bye("invalid set path\n");
+ }
+ if (!cstore.setCfgPath(path_comps)) {
+ bye("set cfg path failed\n");
+ }
+}
+
+static void
+doDelete(Cstore& cstore, const vector<string>& path_comps)
+{
+ vtw_def def;
+ if (!cstore.validateDeletePath(path_comps, def)) {
+ bye("invalid delete path");
+ }
+ if (!cstore.deleteCfgPath(path_comps, def)) {
+ bye("delete failed\n");
+ }
+}
+
+static void
+doActivate(Cstore& cstore, const vector<string>& path_comps)
+{
+ if (!cstore.validateActivatePath(path_comps)) {
+ bye("%s validate failed", OP_str);
+ }
+ if (!cstore.unmarkCfgPathDeactivated(path_comps)) {
+ bye("%s failed", OP_str);
+ }
+}
+
+static void
+doDeactivate(Cstore& cstore, const vector<string>& path_comps)
+{
+ if (!cstore.validateDeactivatePath(path_comps)) {
+ bye("%s validate failed", OP_str);
+ }
+ if (!cstore.markCfgPathDeactivated(path_comps)) {
+ bye("%s failed", OP_str);
+ }
+}
+
+static void
+doRename(Cstore& cstore, const vector<string>& path_comps)
+{
+ if (!cstore.validateRenameArgs(path_comps)) {
+ bye("invalid rename args\n");
+ }
+ if (!cstore.renameCfgPath(path_comps)) {
+ bye("rename cfg path failed\n");
+ }
+}
+
+static void
+doCopy(Cstore& cstore, const vector<string>& path_comps)
+{
+ if (!cstore.validateCopyArgs(path_comps)) {
+ bye("invalid copy args\n");
+ }
+ if (!cstore.copyCfgPath(path_comps)) {
+ bye("copy cfg path failed\n");
+ }
+}
+
+static void
+doComment(Cstore& cstore, const vector<string>& path_comps)
+{
+ vtw_def def;
+ if (!cstore.validateCommentArgs(path_comps, def)) {
+ bye("invalid comment args\n");
+ }
+ if (!cstore.commentCfgPath(path_comps, def)) {
+ bye("comment cfg path failed\n");
+ }
+}
+
+static void
+doDiscard(Cstore& cstore, const vector<string>& args)
+{
+ if (args.size() > 0) {
+ OUTPUT_USER("Invalid discard command\n");
+ bye("invalid discard command\n");
+ }
+ if (!cstore.discardChanges()) {
+ bye("discard failed\n");
+ }
+ // special case for discard: don't return (don't want to mark changed)
+ exit(0);
+}
+
+static void
+doMove(Cstore& cstore, const vector<string>& path_comps)
+{
+ if (!cstore.validateMoveArgs(path_comps)) {
+ bye("invalid move args\n");
+ }
+ if (!cstore.moveCfgPath(path_comps)) {
+ bye("move cfg path failed\n");
+ }
+}
+
+typedef void (*OpFuncT)(Cstore& cstore,
+ const vector<string>& path_comps);
+OpFuncT OpFunc[] = {
+ &doSet,
+ &doDelete,
+ &doActivate,
+ &doDeactivate,
+ &doRename,
+ &doCopy,
+ &doComment,
+ &doDiscard,
+ &doMove,
+ NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ int i = 0;
+ while (op_bin_name[i]) {
+ if (strcmp(basename(argv[0]), op_bin_name[i]) == 0) {
+ op_idx = i;
+ break;
+ }
+ ++i;
+ }
+ if (op_idx == -1) {
+ printf("Invalid operation\n");
+ exit(-1);
+ }
+
+ if (initialize_output(OP_Str) == -1) {
+ bye("can't initialize output\n");
+ }
+ if (OP_need_cfg_node_args && argc < 2) {
+ fprintf(out_stream, "Need to specify the config node to %s\n", OP_str);
+ bye("nothing to %s\n", OP_str);
+ }
+
+ // actual CLI operations use the edit levels from environment, so pass true.
+ UnionfsCstore cstore(true);
+ vector<string> path_comps;
+ for (int i = 1; i < argc; i++) {
+ path_comps.push_back(argv[i]);
+ }
+
+ // call the op function
+ OpFunc[op_idx](cstore, path_comps);
+ cstore.markCfgPathChanged(path_comps);
+ exit(0);
+}
+
diff --git a/src/cli_def.l b/src/cli_def.l
index 86e5c98..806112f 100644
--- a/src/cli_def.l
+++ b/src/cli_def.l
@@ -25,16 +25,19 @@ static int reg_fields_t[] = { DEFAULT, TAG, TYPE, MULTI, PRIORITY, 0 };
static char *act_fields[] = { "help", "syntax", "commit",
"delete", "update", "activate", "create",
"begin", "end",
+ "enumeration",
"comp_help", "allowed", "val_help",
NULL };
static int act_fields_t[] = { HELP, SYNTAX, COMMIT,
ACTION, ACTION, ACTION, ACTION,
ACTION, ACTION,
- DUMMY, DUMMY, DUMMY,
+ ENUMERATION,
+ CHELP, ALLOWED, VHELP,
0 };
static int act_types[] = { -1, -1, -1,
delete_act, update_act, activate_act, create_act,
begin_act, end_act,
+ -1,
-1, -1, -1,
-1 };
@@ -266,7 +269,7 @@ RE_OP_OTHER (pattern|exec|,|\|\||&&|=|!|\(|\)|;)
/* template fields */
RE_REG_FIELD (default|tag|type|multi|priority)
-RE_ACT_FIELD (help|syntax|commit|delete|update|activate|create|begin|end|comp_help|allowed|val_help)
+RE_ACT_FIELD (help|syntax|commit|delete|update|activate|create|begin|end|enumeration|comp_help|allowed|val_help)
%%
diff --git a/src/cli_new.c b/src/cli_new.c
index b0535d8..9cc9777 100644
--- a/src/cli_new.c
+++ b/src/cli_new.c
@@ -21,6 +21,8 @@
#include "cli_objects.h"
#include "cli_val_engine.h"
+#include "cstore/cstore-c.h"
+
/* Defines: */
#define EXE_STRING_DELTA 512
@@ -32,9 +34,12 @@
#define VAR_REF_MARKER "$VAR("
#define VAR_REF_MARKER_LEN 5
+#define VAR_REF_SELF_MARKER "$VAR(@)"
+#define VAR_REF_SELF_MARKER_LEN 7
/* Global vars: */
vtw_path m_path, t_path;
+void *var_ref_handle = NULL;
/* Local vars: */
static vtw_node *vtw_free_nodes; /* linked via left */
@@ -114,8 +119,8 @@ void bye(const char *msg, ...)
{
va_list ap;
- fprintf(out_stream, "%s failed\n",
- (cli_operation_name) ? cli_operation_name : "Operation");
+ OUTPUT_USER("%s failed\n",
+ (cli_operation_name) ? cli_operation_name : "Operation");
va_start(ap, msg);
if (is_echo())
@@ -239,6 +244,74 @@ void append(vtw_list *l, vtw_node *n, int aux)
l->vtw_list_tail = lnode;
}
+/* this recursive function walks the specified "vtw_node" tree representing
+ * "syntax" actions and looks for the first "in" action on a "self ref"
+ * specified as follows in template:
+ * $VAR(@) in "val1", "val2", ...
+ *
+ * if found, the corresponding valstruct is returned. this is used by the
+ * completion mechanism to get such "allowed" values specified by "syntax".
+ */
+const valstruct *
+get_syntax_self_in_valstruct(vtw_node *vnode)
+{
+ const valstruct *ret = NULL;
+ if (!vnode) {
+ return NULL;
+ }
+ if (vnode->vtw_node_oper == COND_OP && vnode->vtw_node_aux == IN_COND
+ && vnode->vtw_node_left && vnode->vtw_node_right) {
+ vtw_node *ln = vnode->vtw_node_left;
+ vtw_node *rn = vnode->vtw_node_right;
+ if (ln->vtw_node_oper == VAR_OP && ln->vtw_node_string
+ && strncmp(VAR_REF_SELF_MARKER, ln->vtw_node_string,
+ VAR_REF_SELF_MARKER_LEN) == 0
+ && rn->vtw_node_oper == VAL_OP) {
+ // found a matching syntax action. return valstruct.
+ return &(rn->vtw_node_val);
+ }
+ }
+ // this node does not match. walk down.
+ ret = get_syntax_self_in_valstruct(vnode->vtw_node_left);
+ if (!ret) {
+ ret = get_syntax_self_in_valstruct(vnode->vtw_node_right);
+ }
+ return ret;
+}
+
+/* execute specified command and return output in specified buffer as
+ * a null-terminated string. return number of characters in the output
+ * or -1 if failed.
+ *
+ * NOTE: NO attempt is made to ensure the security of the specified command.
+ * in other words, *DO NOT* use a user-supplied string as the command
+ * for this function.
+ */
+int
+get_shell_command_output(const char *cmd, char *buf, unsigned int buf_size)
+{
+ int ret = -1;
+ FILE *cmd_in = NULL;
+ size_t cnt = 0;
+
+ if (!buf || !buf_size) {
+ return -1;
+ }
+ if (!(cmd_in = popen(cmd, "r"))) {
+ return -1;
+ }
+ cnt = fread(buf, 1, buf_size - 1, cmd_in);
+ if (cnt == (buf_size - 1) || feof(cmd_in)) {
+ /* buffer full or got the whole output. null terminate */
+ buf[cnt] = 0;
+ ret = cnt;
+ }
+ if (pclose(cmd_in) == -1) {
+ ret = -1;
+ }
+ return ret;
+}
+
void dt(vtw_sorted *srtp)
{
int i;
@@ -729,7 +802,7 @@ int char2val(vtw_def *def, char *value, valstruct *valp)
//currently fails to handle mixed text + non-text case...
char buf1[2048];
if (char2val_notext(def,my_type,my_type2,value,&valp,buf1) != 0) {
- fprintf(out_stream,"%s",buf1);
+ OUTPUT_USER("%s", buf1);
return -1; //only single definition
}
return 0;
@@ -1106,43 +1179,50 @@ static int change_var_value(const char* var_reference,const char* value, int act
int ret=-1;
if(var_reference && value) {
-
- char* var_path=NULL;
+ if (var_ref_handle) {
+ /* XXX current var ref lib implementation is fs-specific.
+ * for now treat it as a part of the unionfs-specific
+ * cstore implementation.
+ * handle is set => we are in cstore operation.
+ */
+ if (cstore_set_var_ref(var_ref_handle, var_reference, value,
+ active_dir)) {
+ ret = 0;
+ }
+ } else {
+ /* legacy usage */
+ char* var_path=NULL;
+ clind_path_ref n_cfg_path=NULL;
+ clind_path_ref n_tmpl_path=NULL;
+ clind_path_ref n_cmd_path=NULL;
- clind_path_ref n_cfg_path=NULL;
- clind_path_ref n_tmpl_path=NULL;
- clind_path_ref n_cmd_path=NULL;
-
- if(set_reference_environment(var_reference,
- &n_cfg_path,
- &n_tmpl_path,
- &n_cmd_path,
- active_dir)==0) {
-
- clind_val cv;
-
- memset(&cv,0,sizeof(cv));
-
- if(clind_config_engine_apply_command_path(n_cfg_path,
- n_tmpl_path,
- n_cmd_path,
- FALSE,
- &cv,
- get_tdirp(),
- TRUE)==0) {
- var_path=cv.value;
-
+ if(set_reference_environment(var_reference,
+ &n_cfg_path,
+ &n_tmpl_path,
+ &n_cmd_path,
+ active_dir)==0) {
+ clind_val cv;
+ memset(&cv,0,sizeof(cv));
+ if(clind_config_engine_apply_command_path(n_cfg_path,
+ n_tmpl_path,
+ n_cmd_path,
+ FALSE,
+ &cv,
+ get_tdirp(),
+ TRUE,
+ active_dir)==0) {
+ var_path=cv.value;
+ }
+ }
+
+ if(n_cfg_path) clind_path_destruct(&n_cfg_path);
+ if(n_tmpl_path) clind_path_destruct(&n_tmpl_path);
+ if(n_cmd_path) clind_path_destruct(&n_cmd_path);
+
+ if(var_path) {
+ ret=write_value_to_file(var_path,value);
+ free(var_path);
}
-
- }
-
- if(n_cfg_path) clind_path_destruct(&n_cfg_path);
- if(n_tmpl_path) clind_path_destruct(&n_tmpl_path);
- if(n_cmd_path) clind_path_destruct(&n_cmd_path);
-
- if(var_path) {
- ret=write_value_to_file(var_path,value);
- free(var_path);
}
}
@@ -1177,7 +1257,7 @@ static boolean check_syn_func(vtw_node *cur,const char **outbuf,const char* func
if (ret <= 0){
if (expand_string(cur->vtw_node_right->vtw_node_string) == VTWERR_OK) {
if (outbuf == NULL) {
- fprintf(out_stream, "%s\n", exe_string);
+ OUTPUT_USER("%s\n", exe_string);
}
else {
strcat((char*)*outbuf, exe_string);
@@ -1362,9 +1442,6 @@ static int eval_va(valstruct *res, vtw_node *node)
{
char *endp = 0;
- clind_path_ref n_cfg_path=NULL;
- clind_path_ref n_tmpl_path=NULL;
- clind_path_ref n_cmd_path=NULL;
pathp = node->vtw_node_string;
DPRINT("eval_va var[%s]\n", pathp);
@@ -1388,36 +1465,63 @@ static int eval_va(valstruct *res, vtw_node *node)
*endp = 0;
- if(set_reference_environment(pathp,
- &n_cfg_path,
- &n_tmpl_path,
- &n_cmd_path,
- is_in_delete_action())==0) {
- clind_val cv;
-
- memset(&cv,0,sizeof(cv));
-
- status=clind_config_engine_apply_command_path(n_cfg_path,
- n_tmpl_path,
- n_cmd_path,
- TRUE,
- &cv,
- get_tdirp(),
- FALSE);
-
- if(status==0) {
- if(cv.value) {
- res->val_type = cv.val_type;
- res->val_types = NULL;
- res->free_me = TRUE;
- res->val = cv.value;
- }
- }
- }
+ if (var_ref_handle) {
+ /* XXX current var ref lib implementation is fs-specific.
+ * for now treat it as a part of the unionfs-specific
+ * cstore implementation.
+ * handle is set => we are in cstore operation.
+ */
+ clind_val cv;
+ if (!cstore_get_var_ref(var_ref_handle, pathp, &cv,
+ is_in_delete_action())) {
+ status = -1;
+ } else {
+ /* success */
+ status = 0;
+ if(cv.value) {
+ res->val_type = cv.val_type;
+ res->val_types = NULL;
+ res->free_me = TRUE;
+ res->val = cv.value;
+ }
+ }
+ } else {
+ /* legacy usage */
+ clind_path_ref n_cfg_path=NULL;
+ clind_path_ref n_tmpl_path=NULL;
+ clind_path_ref n_cmd_path=NULL;
+ if(set_reference_environment(pathp,
+ &n_cfg_path,
+ &n_tmpl_path,
+ &n_cmd_path,
+ is_in_delete_action())==0) {
+ clind_val cv;
+
+ memset(&cv,0,sizeof(cv));
+
+ status=clind_config_engine_apply_command_path(n_cfg_path,
+ n_tmpl_path,
+ n_cmd_path,
+ TRUE,
+ &cv,
+ get_tdirp(),
+ FALSE,
+ is_in_delete_action());
+
+ if(status==0) {
+ if(cv.value) {
+ res->val_type = cv.val_type;
+ res->val_types = NULL;
+ res->free_me = TRUE;
+ res->val = cv.value;
+ }
+ }
+ }
- if(n_cfg_path) clind_path_destruct(&n_cfg_path);
- if(n_tmpl_path) clind_path_destruct(&n_tmpl_path);
- if(n_cmd_path) clind_path_destruct(&n_cmd_path);
+ if(n_cfg_path) clind_path_destruct(&n_cfg_path);
+ if(n_tmpl_path) clind_path_destruct(&n_tmpl_path);
+ if(n_cmd_path) clind_path_destruct(&n_cmd_path);
+ }
*endp = ')';
@@ -1525,11 +1629,6 @@ static int expand_string(char *stringp)
scanp += 4;
} else {
-
- clind_path_ref n_cfg_path=NULL;
- clind_path_ref n_tmpl_path=NULL;
- clind_path_ref n_cmd_path=NULL;
-
char *endp;
endp = strchr(scanp, ')');
@@ -1543,33 +1642,49 @@ static int expand_string(char *stringp)
if (endp == scanp)
bye("Empty path");
- if(set_reference_environment(scanp,
- &n_cfg_path,
- &n_tmpl_path,
- &n_cmd_path,
- is_in_delete_action())==0) {
+ if (var_ref_handle) {
+ /* XXX current var ref lib implementation is fs-specific.
+ * for now treat it as a part of the unionfs-specific
+ * cstore implementation.
+ * handle is set => we are in cstore operation.
+ */
+ clind_val cv;
+ if (cstore_get_var_ref(var_ref_handle, scanp, &cv,
+ is_in_delete_action())) {
+ cp=cv.value;
+ }
+ } else {
+ /* legacy usage */
+ clind_path_ref n_cfg_path=NULL;
+ clind_path_ref n_tmpl_path=NULL;
+ clind_path_ref n_cmd_path=NULL;
+
+ if(set_reference_environment(scanp,
+ &n_cfg_path,
+ &n_tmpl_path,
+ &n_cmd_path,
+ is_in_delete_action())==0) {
+
+ clind_val cv;
+ memset(&cv,0,sizeof(cv));
+ if(clind_config_engine_apply_command_path(n_cfg_path,
+ n_tmpl_path,
+ n_cmd_path,
+ TRUE,
+ &cv,
+ get_tdirp(),
+ FALSE,
+ is_in_delete_action())==0) {
+ cp=cv.value;
+ }
+
+ }
+
+ if(n_cfg_path) clind_path_destruct(&n_cfg_path);
+ if(n_tmpl_path) clind_path_destruct(&n_tmpl_path);
+ if(n_cmd_path) clind_path_destruct(&n_cmd_path);
+ }
- clind_val cv;
-
- memset(&cv,0,sizeof(cv));
-
- if(clind_config_engine_apply_command_path(n_cfg_path,
- n_tmpl_path,
- n_cmd_path,
- TRUE,
- &cv,
- get_tdirp(),
- FALSE)==0) {
- cp=cv.value;
-
- }
-
- }
-
- if(n_cfg_path) clind_path_destruct(&n_cfg_path);
- if(n_tmpl_path) clind_path_destruct(&n_tmpl_path);
- if(n_cmd_path) clind_path_destruct(&n_cmd_path);
-
if(!cp) {
cp="";
} else {
@@ -2047,13 +2162,12 @@ boolean validate_value(vtw_def *def, char *cp)
int i = 0;
for (i = 0; i < strlen(cp); i++) {
if (cp[i] == '\'') {
- fprintf(out_stream, "Cannot use the \"'\" (single quote) character "
- "in a value string\n");
+ OUTPUT_USER("Cannot use the \"'\" (single quote) character "
+ "in a value string\n");
return FALSE;
}
if (cp[i] == '\n') {
- fprintf(out_stream, "Cannot use the newline character "
- "in a value string\n");
+ OUTPUT_USER("Cannot use the newline character in a value string\n");
return FALSE;
}
}
@@ -2062,17 +2176,18 @@ boolean validate_value(vtw_def *def, char *cp)
/* prepare cur_value */
set_at_string(cp);
status = char2val(def, cp, &validate_value_val);
- if (status != VTWERR_OK)
+ if (status != VTWERR_OK) {
return FALSE;
+ }
if ((def->def_type!=ERROR_TYPE) &&
((validate_value_val.val_type != def->def_type) &&
(validate_value_val.val_type != def->def_type2))) {
if (def->def_type_help){
(void)expand_string(def->def_type_help);
- fprintf(out_stream, "%s\n", exe_string);
+ OUTPUT_USER("%s\n", exe_string);
} else {
- fprintf(out_stream, "\"%s\" is not a valid value of type \"%s\"\n",
- cp, type_to_name(def->def_type));
+ OUTPUT_USER("\"%s\" is not a valid value of type \"%s\"\n", cp,
+ type_to_name(def->def_type));
}
ret = FALSE;
goto validate_value_free_and_return;
@@ -2241,7 +2356,7 @@ const char *type_to_name(vtw_type_e type) {
case IPV6NET_TYPE: return("ipv6net");
case MACADDR_TYPE: return("macaddr");
case DOMAIN_TYPE: return("domain");
- case TEXT_TYPE: return("text");
+ case TEXT_TYPE: return("txt");
case BOOL_TYPE: return("bool");
default: return("unknown");
}
@@ -2396,18 +2511,20 @@ restore_output()
/* system_out:
* call system() with output re-enabled.
* output is again redirected before returning from here.
+ * note: this function may be used outside of actual CLI operations, so output
+ * may not have been redirected. check out_stream for such cases.
*/
int
old_system_out(const char *command)
{
int ret = -1;
- if (restore_output() == -1) {
+ if (out_stream && restore_output() == -1) {
return -1;
}
ret = system(command);
- if (redirect_output() == -1) {
+ if (out_stream && redirect_output() == -1) {
return -1;
}
diff --git a/src/cli_parse.y b/src/cli_parse.y
index 318394b..47ba0e5 100644
--- a/src/cli_parse.y
+++ b/src/cli_parse.y
@@ -35,6 +35,10 @@ static void cli_deferror(const char *);
%token HELP
%token DEFAULT
%token PRIORITY
+%token ENUMERATION
+%token CHELP
+%token ALLOWED
+%token VHELP
%token PATTERN
%token EXEC
%token SYNTAX
@@ -150,6 +154,10 @@ type: TYPE TYPE_DEF
cause: help_cause
| default_cause
| priority_stmt
+ | enumeration_stmt
+ | chelp_stmt
+ | allowed_stmt
+ | vhelp_stmt
| syntax_cause
| ACTION action { append(parse_defp->actions + $1, $2, 0);}
| dummy_stmt
@@ -191,6 +199,45 @@ priority_stmt: PRIORITY VALUE
}
}
+enumeration_stmt: ENUMERATION STRING
+ {
+ parse_defp->def_enumeration = $2;
+ }
+
+chelp_stmt: CHELP STRING
+ {
+ parse_defp->def_comp_help = $2;
+ }
+
+allowed_stmt: ALLOWED STRING
+ {
+ parse_defp->def_allowed = $2;
+ }
+
+vhelp_stmt: VHELP STRING
+ {
+ if (!(parse_defp->def_val_help)) {
+ /* first string */
+ parse_defp->def_val_help = $2;
+ } else {
+ /* subsequent strings */
+ char *optr = parse_defp->def_val_help;
+ int olen = strlen(parse_defp->def_val_help);
+ char *nptr = $2;
+ int nlen = strlen(nptr);
+ int len = olen + 1 /* "\n" */ + nlen + 1 /* 0 */;
+ char *mptr = (char *) malloc(len);
+ memcpy(mptr, optr, olen);
+ mptr[olen] = '\n';
+ memcpy(&(mptr[olen + 1]), nptr, nlen);
+ mptr[len - 1] = 0;
+ parse_defp->def_val_help = mptr;
+ free(optr);
+ free(nptr);
+ }
+ /* result is a '\n'-delimited string for val_help */
+ }
+
syntax_cause: SYNTAX exp {append(parse_defp->actions + syntax_act, $2, 0);}
;
diff --git a/src/cli_shell_api.cpp b/src/cli_shell_api.cpp
new file mode 100644
index 0000000..0962c80
--- /dev/null
+++ b/src/cli_shell_api.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <cstring>
+#include <cerrno>
+#include <vector>
+#include <string>
+#include <libgen.h>
+#include <sys/mount.h>
+
+extern "C" {
+#include <cli_val.h>
+}
+
+#include <cstore/unionfs/cstore-unionfs.hpp>
+
+static int op_idx = -1;
+static const char *op_name[] = {
+ "getSessionEnv",
+ "getEditEnv",
+ "getEditUpEnv",
+ "getEditResetEnv",
+ "editLevelAtRoot",
+ "getCompletionEnv",
+ "getEditLevelStr",
+ "markSessionUnsaved",
+ "unmarkSessionUnsaved",
+ "sessionUnsaved",
+ "sessionChanged",
+ "teardownSession",
+ "setupSession",
+ "inSession",
+ NULL
+};
+static const int op_exact_args[] = {
+ 1,
+ -1,
+ 0,
+ 0,
+ 0,
+ -1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ -1
+};
+static const char *op_exact_error[] = {
+ "Must specify session ID",
+ NULL,
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ NULL,
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ "No argument expected",
+ NULL
+};
+static const int op_min_args[] = {
+ -1,
+ 1,
+ -1,
+ -1,
+ -1,
+ 2,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1,
+ -1
+};
+static const char *op_min_error[] = {
+ NULL,
+ "Must specify config path to edit",
+ NULL,
+ NULL,
+ NULL,
+ "Must specify command and at least one component",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+#define OP_exact_args op_exact_args[op_idx]
+#define OP_min_args op_min_args[op_idx]
+#define OP_exact_error op_exact_error[op_idx]
+#define OP_min_error op_min_error[op_idx]
+
+static void
+doGetSessionEnv(const vector<string>& args)
+{
+ string env;
+ UnionfsCstore cstore(args[0], env);
+ printf("%s", env.c_str());
+}
+
+static void
+doGetEditEnv(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ string env;
+ if (!cstore.getEditEnv(args, env)) {
+ exit(-1);
+ }
+ printf("%s", env.c_str());
+}
+
+static void
+doGetEditUpEnv(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ string env;
+ if (!cstore.getEditUpEnv(env)) {
+ exit(-1);
+ }
+ printf("%s", env.c_str());
+}
+
+static void
+doGetEditResetEnv(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ string env;
+ if (!cstore.getEditResetEnv(env)) {
+ exit(-1);
+ }
+ printf("%s", env.c_str());
+}
+
+static void
+doEditLevelAtRoot(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ exit(cstore.editLevelAtRoot() ? 0 : 1);
+}
+
+static void
+doGetCompletionEnv(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ string env;
+ if (!cstore.getCompletionEnv(args, env)) {
+ exit(-1);
+ }
+ printf("%s", env.c_str());
+}
+
+static void
+doGetEditLevelStr(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ vector<string> lvec;
+ cstore.getEditLevel(lvec);
+ string level;
+ for (unsigned int i = 0; i < lvec.size(); i++) {
+ if (level.length() > 0) {
+ level += " ";
+ }
+ level += lvec[i];
+ }
+ printf("%s", level.c_str());
+}
+
+static void
+doMarkSessionUnsaved(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.markSessionUnsaved()) {
+ exit(-1);
+ }
+}
+
+static void
+doUnmarkSessionUnsaved(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.unmarkSessionUnsaved()) {
+ exit(-1);
+ }
+}
+
+static void
+doSessionUnsaved(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.sessionUnsaved()) {
+ exit(-1);
+ }
+}
+
+static void
+doSessionChanged(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.sessionChanged()) {
+ exit(-1);
+ }
+}
+
+static void
+doTeardownSession(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.teardownSession()) {
+ exit(-1);
+ }
+}
+
+static void
+doSetupSession(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.setupSession()) {
+ exit(-1);
+ }
+}
+
+static void
+doInSession(const vector<string>& args)
+{
+ UnionfsCstore cstore(true);
+ if (!cstore.inSession()) {
+ exit(-1);
+ }
+}
+
+typedef void (*OpFuncT)(const vector<string>& args);
+OpFuncT OpFunc[] = {
+ &doGetSessionEnv,
+ &doGetEditEnv,
+ &doGetEditUpEnv,
+ &doGetEditResetEnv,
+ &doEditLevelAtRoot,
+ &doGetCompletionEnv,
+ &doGetEditLevelStr,
+ &doMarkSessionUnsaved,
+ &doUnmarkSessionUnsaved,
+ &doSessionUnsaved,
+ &doSessionChanged,
+ &doTeardownSession,
+ &doSetupSession,
+ &doInSession,
+ NULL
+};
+
+int
+main(int argc, char **argv)
+{
+ int i = 0;
+ if (argc < 2) {
+ printf("Must specify operation\n");
+ exit(-1);
+ }
+ while (op_name[i]) {
+ if (strcmp(argv[1], op_name[i]) == 0) {
+ op_idx = i;
+ break;
+ }
+ ++i;
+ }
+ if (op_idx == -1) {
+ printf("Invalid operation\n");
+ exit(-1);
+ }
+ if (OP_exact_args >= 0 && (argc - 2) != OP_exact_args) {
+ printf("%s\n", OP_exact_error);
+ exit(-1);
+ }
+ if (OP_min_args >= 0 && (argc - 2) < OP_min_args) {
+ printf("%s\n", OP_min_error);
+ exit(-1);
+ }
+
+ vector<string> args;
+ for (int i = 2; i < argc; i++) {
+ args.push_back(argv[i]);
+ }
+
+ // call the op function
+ OpFunc[op_idx](args);
+ exit(0);
+}
+
diff --git a/src/cli_val.h b/src/cli_val.h
index 6918fcf..41e4461 100644
--- a/src/cli_val.h
+++ b/src/cli_val.h
@@ -111,11 +111,17 @@ typedef struct {
char *def_default;
unsigned int def_priority;
char *def_priority_ext;
+ char *def_enumeration;
+ char *def_comp_help;
+ char *def_allowed;
+ char *def_val_help;
unsigned int def_tag;
unsigned int def_multi;
boolean tag;
boolean multi;
vtw_list actions[top_act];
+ int is_value; /* this is used by the config store to indicate whether
+ * the last path component is a "value". */
}vtw_def;
typedef struct {
@@ -156,6 +162,9 @@ extern vtw_node *make_str_node(char *str);
extern vtw_node *make_var_node(char *str);
extern vtw_node *make_str_node0(char *str, vtw_oper_e op);
extern void append(vtw_list *l, vtw_node *n, int aux);
+const valstruct *get_syntax_self_in_valstruct(vtw_node *vnode);
+int get_shell_command_output(const char *cmd, char *buf,
+ unsigned int buf_size);
extern int parse_def(vtw_def *defp, const char *path, boolean type_only);
extern int yy_cli_val_lex(void);
@@ -169,6 +178,7 @@ extern void free_def(vtw_def *defp);
extern void free_sorted(vtw_sorted *sortp);
extern vtw_path m_path, t_path;
+extern void *var_ref_handle;
/*************************************************
GLOBAL FUNCTIONS
@@ -231,6 +241,15 @@ extern FILE *out_stream;
extern FILE *err_stream;
extern int initialize_output(const char *op);
+/* note that some functions may be used outside the actual CLI operations,
+ * so output may not have been initialized. nop in such cases.
+ */
+#define OUTPUT_USER(fmt, args...) do \
+ { \
+ if (out_stream) { \
+ fprintf(out_stream, fmt , ##args); \
+ } \
+ } while (0);
/* debug hooks? */
#define my_malloc(size, name) malloc(size)
diff --git a/src/cli_val_engine.c b/src/cli_val_engine.c
index 00e891b..b481b0d 100644
--- a/src/cli_val_engine.c
+++ b/src/cli_val_engine.c
@@ -54,13 +54,15 @@
#include <string.h>
+#include <cstore/cstore-c.h>
+
+#include "cli_objects.h"
#include "cli_val_engine.h"
static int is_multi_node(clind_path_ref tmpl_path);
-static boolean
-is_deactivated(const char *path);
+static boolean is_deactivated(const char *path, int in_active);
/*********************
* Data definitions
@@ -120,6 +122,8 @@ static int clind_path_shift_cmd(clind_path_ref path,clind_cmd *cmd);
* If path is empty, or the file is empty, or the file does not exist,
* then return NULL.
* The user of this function is responsible for the memory deallocation.
+ *
+ * in_active is for the deactivated check.
*/
static char** clind_get_current_value(clind_path_ref cfg_path,
@@ -129,7 +133,7 @@ static char** clind_get_current_value(clind_path_ref cfg_path,
const char* root_tmpl_path,
int return_value_file_name,
int multi_value,
- int *ret_size) {
+ int *ret_size, int in_active) {
char** ret=NULL;
int value_ref = 0;
@@ -181,7 +185,8 @@ static char** clind_get_current_value(clind_path_ref cfg_path,
} else {
- if (is_deactivated(clind_path_get_path_string(cfg_path)) == FALSE) {
+ if (is_deactivated(clind_path_get_path_string(cfg_path), in_active)
+ == FALSE) {
FILE* f = fopen(cfg_path_string,"r");
if(f) {
char buffer[8193];
@@ -233,7 +238,8 @@ static char** clind_get_current_value(clind_path_ref cfg_path,
/* Directory reference: */
- if(!check_existence || ((lstat(cfg_path_string, &statbuf) == 0) && is_deactivated(cfg_path_string) == FALSE)) {
+ if(!check_existence || ((lstat(cfg_path_string, &statbuf) == 0)
+ && is_deactivated(cfg_path_string, in_active) == FALSE)) {
ret=(char**)realloc(ret,sizeof(char*)*1);
ret[0]=clind_unescape(cfg_end);
*ret_size=1;
@@ -278,7 +284,7 @@ static char** clind_get_current_value(clind_path_ref cfg_path,
strcpy(fn_node_def+strlen(fn_node_def),NODE_DEF);
if ((lstat(fn_node_def, &statbuf) == 0)&&
- (is_deactivated(fn_node_def) == FALSE)&&
+ (is_deactivated(fn_node_def, in_active) == FALSE)&&
(parse_def(&def, fn_node_def, TRUE)==0)) {
if(def.def_type != ERROR_TYPE) {
@@ -614,9 +620,15 @@ static clind_path_ref* clind_config_engine_apply_command(clind_path_ref cfg_path
* handled before this is called (see set_reference_environment() in
* cli_new.c). cli_new.c was passing the wrong path (changes only)
* anyway, causing problems with absolute paths (bug 5213).
+ *
* root_tmpl_path should not be necessary either but it's
* used in one place in clind_get_current_value() (it's not clear
* if that case is ever reached), so keep it for now.
+ *
+ * in_active is needed for the deactivated check. if operating on
+ * active, deactivated check should be in active as well. this
+ * makes a difference for nodes that are being deactivated (i.e.,
+ * deactivated in working but not in active).
*/
int clind_config_engine_apply_command_path(clind_path_ref cfg_path_orig,
@@ -625,7 +637,8 @@ int clind_config_engine_apply_command_path(clind_path_ref cfg_path_orig,
int check_existence,
clind_val* res,
const char* root_tmpl_path,
- int return_value_file_name) {
+ int return_value_file_name,
+ int in_active) {
int ret=-1;
@@ -758,7 +771,7 @@ int clind_config_engine_apply_command_path(clind_path_ref cfg_path_orig,
return_value_file_name,
/*Last command: */
(cmd.type==CLIND_CMD_MULTI_VALUE),
- &vallen);
+ &vallen, in_active);
clind_path_destruct(&config_paths[i]);
@@ -925,57 +938,38 @@ static int clind_path_shift_cmd(clind_path_ref path,clind_cmd *cmd) {
boolean
-is_deactivated(const char *path_string)
+is_deactivated(const char *path_string, int in_active)
{
- if (path_string == NULL) {
- return FALSE;
- }
-
- // const char* path_string = clind_path_get_path_string(*path);
-
- char buf[1024]; //ALSO USED AS LIMIT IN UNIONFS path length
- strcpy(buf,path_string);
-
- //first we'll check the current directory
- char file[1024];
- sprintf(file,"%s/.disable",buf);
- struct stat s;
- if (lstat(file,&s) == 0) {
- return TRUE;
- }
-
- long min_len = strlen("/opt/vyatta/config/tmp/new_config_");
-
- //now walk back up tree looking for disable flag.....
- while (TRUE) {
- int index = strlen(buf)-1;
- if (index < min_len) {
- return FALSE;
- }
- if (buf[index] == '/') {
- while (TRUE) {
- if (buf[--index] != '/') {
- buf[index] = '\0';
- break;
- }
- }
- }
-
- char *ptr = rindex(buf,'/');
- if (ptr != NULL) {
- *ptr = '\0';
- }
-
- char file[1024];
- sprintf(file,"%s/.disable",buf);
-
- // fprintf(out_stream,"checking file for disable: %s\n",file);
-
- struct stat s;
- if (lstat(file,&s) == 0) {
- return TRUE;
- }
+ int num = 0;
+ boolean ret = FALSE;
+ char **path_comps = cstore_path_string_to_path_comps(path_string, &num);
+ void *csh = cstore_init();
+
+ /* XXX this lib should operate on "logical paths" only, but currently
+ * it is using physical paths. convert to logical paths (remove the
+ * prefix) to use the cstore library.
+ *
+ * XXX also, use is_in_delete_action() as in_active arg for the call.
+ * this follows the original behavior that when in delete action, var
+ * refs are obtained from the active config. the result of the original
+ * behavior is that if a node is being deleted (i.e., in active but not
+ * in working), a var ref for the node will still return the active value.
+ *
+ * by passing is_in_delete_action() here, the result is that if a node is
+ * being deactivated (i.e., deactivated in working but not in active),
+ * a var ref for the node will still return the active value.
+ *
+ * the original behavior may not be correct, but for compatibility
+ * it's emulated here. should revisit later.
+ */
+ if (num > 5
+ && cstore_cfg_path_deactivated(csh,
+ (const char **) &(path_comps[5]),
+ num - 5, in_active)) {
+ ret = TRUE;
}
-
- return FALSE;
+ cstore_free_path_comps(path_comps, num);
+ cstore_free(csh);
+ return ret;
}
+
diff --git a/src/cli_val_engine.h b/src/cli_val_engine.h
index a0c1fe3..4f35a7f 100644
--- a/src/cli_val_engine.h
+++ b/src/cli_val_engine.h
@@ -35,8 +35,8 @@
#if !defined(__CLI_VAL_ENGINE__)
#define __CLI_VAL_ENGINE__
-#include "cli_path_utils.h"
-#include "cli_val.h"
+#include <cli_path_utils.h>
+#include <cli_val.h>
/*******************
* Type definitions
@@ -81,7 +81,8 @@ int clind_config_engine_apply_command_path(clind_path_ref cfg_path,
int check_existence,
clind_val *res,
const char* root_tmpl_path,
- int return_value_file_name);
+ int return_value_file_name,
+ int in_active);
diff --git a/src/cstore/cstore-c.cpp b/src/cstore/cstore-c.cpp
new file mode 100644
index 0000000..3215707
--- /dev/null
+++ b/src/cstore/cstore-c.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstring>
+#include <vector>
+#include <string>
+
+#include "cstore-c.h"
+#include "cstore/unionfs/cstore-unionfs.hpp"
+
+void *
+cstore_init(void)
+{
+ Cstore *handle = new UnionfsCstore();
+ return (void *) handle;
+}
+
+void
+cstore_free(void *handle)
+{
+ UnionfsCstore *h = (UnionfsCstore *) handle;
+ delete h;
+}
+
+int
+cstore_validate_tmpl_path(void *handle, const char *path_comps[],
+ int num_comps, int validate_tags)
+{
+ if (handle) {
+ vector<string> vs;
+ for (int i = 0; i < num_comps; i++) {
+ vs.push_back(path_comps[i]);
+ }
+ Cstore *cs = (Cstore *) handle;
+ return (cs->validateTmplPath(vs, validate_tags) ? 1 : 0);
+ }
+ return 0;
+}
+
+int
+cstore_validate_tmpl_path_d(void *handle, const char *path_comps[],
+ int num_comps, int validate_tags, vtw_def *def)
+{
+ if (handle) {
+ vector<string> vs;
+ for (int i = 0; i < num_comps; i++) {
+ vs.push_back(path_comps[i]);
+ }
+ Cstore *cs = (Cstore *) handle;
+ return (cs->validateTmplPath(vs, validate_tags, *def) ? 1 : 0);
+ }
+ return 0;
+}
+
+int
+cstore_cfg_path_exists(void *handle, const char *path_comps[], int num_comps)
+{
+ if (handle) {
+ vector<string> vs;
+ for (int i = 0; i < num_comps; i++) {
+ vs.push_back(path_comps[i]);
+ }
+ Cstore *cs = (Cstore *) handle;
+ return (cs->cfgPathExists(vs) ? 1 : 0);
+ }
+ return 0;
+}
+
+int
+cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval,
+ int from_active)
+{
+ if (handle) {
+ Cstore *cs = (Cstore *) handle;
+ return (cs->getVarRef(ref_str, *cval, from_active) ? 1 : 0);
+ }
+ return 0;
+}
+
+int
+cstore_set_var_ref(void *handle, const char *ref_str, const char *value,
+ int to_active)
+{
+ if (handle) {
+ Cstore *cs = (Cstore *) handle;
+ return (cs->setVarRef(ref_str, value, to_active) ? 1 : 0);
+ }
+ return 0;
+}
+
+int
+cstore_cfg_path_deactivated(void *handle, const char *path_comps[],
+ int num_comps, int in_active)
+{
+ if (handle) {
+ vector<string> vs;
+ for (int i = 0; i < num_comps; i++) {
+ vs.push_back(path_comps[i]);
+ }
+ Cstore *cs = (Cstore *) handle;
+ return (cs->cfgPathDeactivated(vs, in_active) ? 1 : 0);
+ }
+ return 0;
+}
+
+char **
+cstore_path_string_to_path_comps(const char *path_str, int *num_comps)
+{
+ char *pstr = strdup(path_str);
+ size_t len = strlen(pstr);
+ vector<string> vec;
+ char *start = NULL;
+ for (unsigned int i = 0; i < len; i++) {
+ if (pstr[i] == '/') {
+ if (start) {
+ pstr[i] = 0;
+ vec.push_back(start);
+ pstr[i] = '/';
+ start = NULL;
+ }
+ continue;
+ } else if (!start) {
+ start = &(pstr[i]);
+ }
+ }
+ if (start) {
+ vec.push_back(start);
+ }
+ char **ret = (char **) malloc(sizeof(char *) * vec.size());
+ for (unsigned int i = 0; i < vec.size(); i++) {
+ ret[i] = strdup(vec[i].c_str());
+ }
+ *num_comps = vec.size();
+ free(pstr);
+ return ret;
+}
+
+void
+cstore_free_path_comps(char **path_comps, int num_comps)
+{
+ for (int i = 0; i < num_comps; i++) {
+ free(path_comps[i]);
+ }
+ free(path_comps);
+}
+
diff --git a/src/cstore/cstore-c.h b/src/cstore/cstore-c.h
new file mode 100644
index 0000000..e664f95
--- /dev/null
+++ b/src/cstore/cstore-c.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CSTORE_C_H_
+#define _CSTORE_C_H_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <cli_val.h>
+#include <cli_val_engine.h>
+
+void *cstore_init(void);
+void cstore_free(void *handle);
+int cstore_validate_tmpl_path(void *handle, const char *path_comps[],
+ int num_comps, int validate_tags);
+int cstore_validate_tmpl_path_d(void *handle, const char *path_comps[],
+ int num_comps, int validate_tags,
+ vtw_def *def);
+int cstore_cfg_path_exists(void *handle, const char *path_comps[],
+ int num_comps);
+int cstore_cfg_path_deactivated(void *handle, const char *path_comps[],
+ int num_comps, int in_active);
+
+/* the following are internal APIs for the library. they can only be used
+ * during cstore operations since they operate on "current" paths constructed
+ * by the operations.
+ */
+int cstore_get_var_ref(void *handle, const char *ref_str, clind_val *cval,
+ int from_active);
+int cstore_set_var_ref(void *handle, const char *ref_str, const char *value,
+ int to_active);
+
+/* util functions */
+char **cstore_path_string_to_path_comps(const char *path_str, int *num_comps);
+void cstore_free_path_comps(char **path_comps, int num_comps);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _CSTORE_C_H_ */
+
diff --git a/src/cstore/cstore-varref.cpp b/src/cstore/cstore-varref.cpp
new file mode 100644
index 0000000..6d71307
--- /dev/null
+++ b/src/cstore/cstore-varref.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <vector>
+#include <string>
+#include <algorithm>
+
+#include "cstore-varref.hpp"
+
+extern "C" {
+#include "cli_val.h"
+#include "cli_objects.h"
+}
+
+using namespace std;
+
+////// constructors/destructors
+Cstore::VarRef::VarRef(Cstore *cstore, const string& ref_str, bool active)
+ : _cstore(cstore), _active(active)
+{
+ /* NOTE: this class will change the paths in the cstore. caller must do
+ * save/restore for the cstore if necessary.
+ */
+ if (!_cstore) {
+ // no cstore
+ return;
+ }
+
+ _absolute = (ref_str[0] == '/');
+ while (!_absolute && !_cstore->cfg_path_at_root()) {
+ _orig_path_comps.insert(_orig_path_comps.begin(),
+ _cstore->pop_cfg_path());
+ }
+ _cstore->reset_paths();
+ /* at this point, cstore paths are at root. _orig_path_comps contains
+ * the path originally in cstore (or empty if _absolute).
+ */
+
+ size_t si = (_absolute ? 1 : 0);
+ size_t sn = 0;
+ vector<string> rcomps;
+ while (si < ref_str.length()
+ && (sn = ref_str.find('/', si)) != ref_str.npos) {
+ rcomps.push_back(ref_str.substr(si, sn - si));
+ si = sn + 1;
+ }
+ if (si < ref_str.length()) {
+ rcomps.push_back(ref_str.substr(si));
+ }
+ // NOTE: if path ends in '/', the trailing slash is ignored.
+
+ // get the "at" string. this is set inside cli_new.c.
+ _at_string = get_at_string();
+
+ // process ref
+ vector<string> pcomps = _orig_path_comps;
+ process_ref(rcomps, pcomps, ERROR_TYPE);
+}
+
+/* process the reference(s).
+ * this is a recursive function and always keeps the cstore paths unchanged
+ * between invocations.
+ *
+ * note: def_type is added into _paths along with the paths. when it's
+ * ERROR_TYPE, it means the path needs to be checked for existence.
+ * otherwise, the path is a "value" (or "values") read from the
+ * actual config (working or active).
+ */
+void
+Cstore::VarRef::process_ref(const vector<string>& ref_comps,
+ const vector<string>& cur_path_comps,
+ vtw_type_e def_type)
+{
+ if (ref_comps.size() == 0) {
+ // done
+ _paths.push_back(pair<vector<string>,
+ vtw_type_e>(cur_path_comps, def_type));
+ return;
+ }
+
+ vector<string> rcomps = ref_comps;
+ vector<string> pcomps = cur_path_comps;
+ string cr_comp= rcomps.front();
+ rcomps.erase(rcomps.begin());
+
+ vtw_def def;
+ bool got_tmpl = _cstore->get_parsed_tmpl(pcomps, false, def);
+ bool handle_leaf = false;
+ if (cr_comp == "@") {
+ if (!got_tmpl) {
+ // invalid path
+ return;
+ }
+ if (def.def_type == ERROR_TYPE) {
+ // no value for typeless node
+ return;
+ }
+ if (pcomps.size() == _orig_path_comps.size()) {
+ if (pcomps.size() == 0
+ || equal(pcomps.begin(), pcomps.end(), _orig_path_comps.begin())) {
+ /* we are at the original path. this is a self-reference, e.g.,
+ * $VAR(@), so use the "at string".
+ */
+ pcomps.push_back(_at_string);
+ process_ref(rcomps, pcomps, def.def_type);
+ return;
+ }
+ }
+ if (pcomps.size() < _orig_path_comps.size()) {
+ // within the original path. @ translates to the path comp.
+ pcomps.push_back(_orig_path_comps[pcomps.size()]);
+ process_ref(rcomps, pcomps, def.def_type);
+ return;
+ }
+ if (def.is_value || def.tag) {
+ // invalid ref
+ return;
+ }
+ // handle leaf node
+ handle_leaf = true;
+ } else if (cr_comp == ".") {
+ process_ref(rcomps, pcomps, ERROR_TYPE);
+ } else if (cr_comp == "..") {
+ if (!got_tmpl || pcomps.size() == 0) {
+ // invalid path
+ return;
+ }
+ pcomps.pop_back();
+ if (!_cstore->get_parsed_tmpl(pcomps, false, def)) {
+ // invalid tmpl path
+ return;
+ }
+ if (def.is_value && def.tag) {
+ // at "tag value", need to pop one more.
+ if (pcomps.size() == 0) {
+ // invalid path
+ return;
+ }
+ pcomps.pop_back();
+ }
+ process_ref(rcomps, pcomps, ERROR_TYPE);
+ } else if (cr_comp == "@@") {
+ if (!got_tmpl) {
+ // invalid path
+ return;
+ }
+ if (def.def_type == ERROR_TYPE) {
+ // no value for typeless node
+ return;
+ }
+ if (def.is_value) {
+ // invalid ref
+ return;
+ }
+ if (def.tag) {
+ // tag node
+ vector<string> cnodes;
+ _cstore->cfgPathGetChildNodes(pcomps, cnodes, _active);
+ for (unsigned int i = 0; i < cnodes.size(); i++) {
+ pcomps.push_back(cnodes[i]);
+ process_ref(rcomps, pcomps, def.def_type);
+ pcomps.pop_back();
+ }
+ } else {
+ // handle leaf node
+ handle_leaf = true;
+ }
+ } else {
+ // just text. go down 1 level.
+ if (got_tmpl && def.tag && !def.is_value) {
+ // at "tag node". need to go down 1 more level.
+ if (pcomps.size() >= _orig_path_comps.size()) {
+ // already under the original node. invalid ref.
+ return;
+ }
+ // within the original path. take the original tag value.
+ pcomps.push_back(_orig_path_comps[pcomps.size()]);
+ }
+ pcomps.push_back(cr_comp);
+ process_ref(rcomps, pcomps, ERROR_TYPE);
+ }
+
+ if (handle_leaf) {
+ if (def.multi) {
+ // multi-value node
+ vector<string> vals;
+ if (!_cstore->cfgPathGetValues(pcomps, vals, _active)) {
+ return;
+ }
+ string val;
+ for (unsigned int i = 0; i < vals.size(); i++) {
+ if (val.length() > 0) {
+ val += " ";
+ }
+ val += vals[i];
+ }
+ pcomps.push_back(val);
+ // treat "joined" multi-values as TEXT_TYPE
+ _paths.push_back(pair<vector<string>, vtw_type_e>(pcomps, TEXT_TYPE));
+ // at leaf. stop recursion.
+ } else {
+ // single-value node
+ string val;
+ if (!_cstore->cfgPathGetValue(pcomps, val, _active)) {
+ return;
+ }
+ pcomps.push_back(val);
+ _paths.push_back(pair<vector<string>, vtw_type_e>(pcomps, def.def_type));
+ // at leaf. stop recursion.
+ }
+ }
+}
+
+bool
+Cstore::VarRef::getValue(string& value, vtw_type_e& def_type)
+{
+ vector<string> result;
+ map<string, bool> added;
+ def_type = ERROR_TYPE;
+ for (unsigned int i = 0; i < _paths.size(); i++) {
+ if (_paths[i].first.size() == 0) {
+ // empty path
+ continue;
+ }
+ if (added.find(_paths[i].first.back()) != added.end()) {
+ // already added
+ continue;
+ }
+ if (_paths[i].second == ERROR_TYPE
+ && !_cstore->cfgPathExists(_paths[i].first, _active)) {
+ // path doesn't exist
+ continue;
+ }
+ if (_paths[i].second != ERROR_TYPE) {
+ // set def_type. all types should be the same if multiple entries exist.
+ def_type = _paths[i].second;
+ }
+ added[_paths[i].first.back()] = true;
+ result.push_back(_paths[i].first.back());
+ }
+ if (result.size() == 0) {
+ // got nothing
+ return false;
+ }
+ if (result.size() > 1 || def_type == ERROR_TYPE) {
+ /* if no type is available or we are returning "joined" multiple values,
+ * treat it as text type.
+ */
+ def_type = TEXT_TYPE;
+ }
+ value = "";
+ for (unsigned int i = 0; i < result.size(); i++) {
+ if (i > 0) {
+ value += " ";
+ }
+ value += result[i];
+ }
+ return true;
+}
+
+bool
+Cstore::VarRef::getSetPath(vector<string>& path_comps)
+{
+ /* XXX this function is currently unused and untested. see setVarRef()
+ * in Cstore for more information.
+ */
+ if (_paths.size() != 1) {
+ // for set_var_ref operation, there can be only one path.
+ return false;
+ }
+ path_comps = _paths[0].first;
+ return true;
+}
+
diff --git a/src/cstore/cstore-varref.hpp b/src/cstore/cstore-varref.hpp
new file mode 100644
index 0000000..1fc1d52
--- /dev/null
+++ b/src/cstore/cstore-varref.hpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CSTORE_VARREF_H_
+#define _CSTORE_VARREF_H_
+#include <vector>
+#include <string>
+#include <map>
+
+#include "cstore.hpp"
+
+using namespace std;
+
+class Cstore::VarRef {
+public:
+ VarRef(Cstore *cstore, const string& ref_str, bool active);
+ ~VarRef() {};
+
+ bool getValue(string& value, vtw_type_e& def_type);
+ bool getSetPath(vector<string>& path_comps);
+
+private:
+ Cstore *_cstore;
+ bool _active;
+ bool _absolute;
+ string _at_string;
+ vector<string> _orig_path_comps;
+ vector<pair<vector<string>, vtw_type_e> > _paths;
+
+ void process_ref(const vector<string>& ref_comps,
+ const vector<string>& cur_path_comps, vtw_type_e def_type);
+};
+
+#endif /* _CSTORE_VARREF_H_ */
+
diff --git a/src/cstore/cstore.cpp b/src/cstore/cstore.cpp
new file mode 100644
index 0000000..31c896a
--- /dev/null
+++ b/src/cstore/cstore.cpp
@@ -0,0 +1,2496 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <cstdarg>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <map>
+#include <algorithm>
+#include <sstream>
+
+extern "C" {
+#include "cli_val.h"
+}
+
+#include "cstore.hpp"
+#include "cstore-varref.hpp"
+
+
+////// constants
+//// node status
+const string Cstore::C_NODE_STATUS_DELETED = "deleted";
+const string Cstore::C_NODE_STATUS_ADDED = "added";
+const string Cstore::C_NODE_STATUS_CHANGED = "changed";
+const string Cstore::C_NODE_STATUS_STATIC = "static";
+
+//// env vars for shell
+// current levels
+const string Cstore::C_ENV_EDIT_LEVEL = "VYATTA_EDIT_LEVEL";
+const string Cstore::C_ENV_TMPL_LEVEL = "VYATTA_TEMPLATE_LEVEL";
+
+// shell-specific vars
+const string Cstore::C_ENV_SHELL_PROMPT = "PS1";
+const string Cstore::C_ENV_SHELL_CWORDS = "COMP_WORDS";
+const string Cstore::C_ENV_SHELL_CWORD_COUNT = "COMP_CWORD";
+
+// shell api vars
+const string Cstore::C_ENV_SHAPI_COMP_VALS = "_cli_shell_api_comp_values";
+const string Cstore::C_ENV_SHAPI_LCOMP_VAL = "_cli_shell_api_last_comp_val";
+const string Cstore::C_ENV_SHAPI_COMP_HELP = "_cli_shell_api_comp_help";
+const string Cstore::C_ENV_SHAPI_HELP_ITEMS = "_cli_shell_api_hitems";
+const string Cstore::C_ENV_SHAPI_HELP_STRS = "_cli_shell_api_hstrs";
+
+//// dirs
+const string Cstore::C_ENUM_SCRIPT_DIR = "/opt/vyatta/share/enumeration";
+
+////// constructors/destructors
+/* this constructor just returns the generic environment string,
+ * currently the two levels. implementation-specific environment
+ * (e.g., unionfs stuff) is handled by derived class.
+ *
+ * note: currently using original semantics for the levels, i.e., they
+ * represent the actual physical paths, which involve fs-specific
+ * escaping. this should be changed to a "logical" representation
+ * so that their manipulation can be moved from derived class to
+ * this base class.
+ */
+Cstore::Cstore(string& env)
+{
+ string decl = "declare -x ";
+ env = (decl + C_ENV_EDIT_LEVEL + "=/; ");
+ env += (decl + C_ENV_TMPL_LEVEL + "=/;");
+}
+
+
+////// public interface
+/* check if specified "logical path" corresponds to a valid template.
+ * validate_vals: whether to validate "values" along specified path.
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateTmplPath(const vector<string>& 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<string>& 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<string>& path_comps,
+ map<string, string>& 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<string>& path_comps,
+ vector<string>& cnodes)
+{
+ SAVE_PATHS;
+ append_tmpl_path(path_comps);
+ get_all_tmpl_child_node_names(cnodes);
+ RESTORE_PATHS;
+}
+
+/* delete specified "logical path" from "working config".
+ * def: parsed template corresponding to logical path path_comps.
+ * return true if successful. otherwise return false.
+ * note: assume specified path has been validated
+ * (i.e., validateDeletePath()).
+ */
+bool
+Cstore::deleteCfgPath(const vector<string>& path_comps, const vtw_def& def)
+{
+ if (!cfg_path_exists(path_comps, false, true)) {
+ output_user("Nothing to delete (the specified %s does not exist)\n",
+ (!def.is_value || def.tag) ? "node" : "value");
+ // treat as success
+ return true;
+ }
+
+ /* path already validated and in working config.
+ * cases:
+ * 1. has default value
+ * => replace current value with default
+ * 2. no default value
+ * => remove config path
+ */
+ if (def.def_default) {
+ // case 1. construct path for value file.
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ if (def.is_value) {
+ // last comp is "value". need to go up 1 level.
+ pop_cfg_path();
+ }
+
+ /* assume default value is valid (parser should have validated).
+ * also call unmark_deactivated() in case the node being deleted was
+ * also deactivated. note that unmark_deactivated() succeeds if it's
+ * not marked deactivated.
+ */
+ bool ret = (write_value(def.def_default) && mark_display_default()
+ && unmark_deactivated());
+ if (!ret) {
+ output_user("Failed to set default value during delete\n");
+ }
+ RESTORE_PATHS;
+ return ret;
+ }
+
+ /* case 2.
+ * sub-cases:
+ * (1) last path comp is "value", i.e., tag (value of tag node),
+ * value of single-value node, or value of multi-value node.
+ * (a) value of single-value node
+ * => remove node
+ * (b) value of multi-value node
+ * => remove value. remove node if last value.
+ * (c) value of tag node (i.e., tag)
+ * => remove tag. remove node if last tag.
+ * (2) last path comp is "node", i.e., typeless node, tag node,
+ * single-value node, or multi-value node.
+ * => remove node
+ */
+ bool ret = false;
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ if (!def.is_value) {
+ // sub-case (2)
+ ret = remove_node();
+ } else {
+ // last comp is value
+ if (def.tag) {
+ // sub-case (1c)
+ ret = remove_tag();
+ } else if (def.multi) {
+ // sub-case (1b)
+ pop_cfg_path();
+ ret = remove_value_from_multi(path_comps[path_comps.size() - 1]);
+ } else {
+ // sub-case (1a). delete node at 1 level up.
+ pop_cfg_path();
+ ret = remove_node();
+ }
+ }
+ RESTORE_PATHS;
+ if (!ret) {
+ output_user("Failed to delete specified config path\n");
+ }
+ return ret;
+}
+
+/* check if specified "logical path" is valid for "set" operation
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateSetPath(const vector<string>& path_comps)
+{
+ // if we can get parsed tmpl, path is valid
+ vtw_def def;
+ string terr;
+ if (!get_parsed_tmpl(path_comps, true, def, terr)) {
+ output_user("%s\n", terr.c_str());
+ return false;
+ }
+
+ bool ret = true;
+ SAVE_PATHS;
+ if (!def.is_value) {
+ if (def.def_type != ERROR_TYPE) {
+ /* disallow setting value node without value
+ * note: different from old behavior, which only disallow setting a
+ * single-value node without value. now all value nodes
+ * (single-value, multi-value, and tag) must be set with value.
+ */
+ output_user("The specified configuration node requires a value\n");
+ ret = false;
+ } else {
+ /* typeless node
+ * note: XXX the following is present in the original logic, perhaps
+ * to trigger check_syn() on the typeless node? is this really
+ * necessary?
+ * also, validate_val() uses current cfg path and tmpl path, so
+ * construct them before calling it.
+ */
+ append_cfg_path(path_comps);
+ append_tmpl_path(path_comps);
+ if (!validate_val(&def, "")) {
+ ret = false;
+ }
+ }
+ }
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* check if specified "logical path" is valid for "delete" operation
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateDeletePath(const vector<string>& path_comps, vtw_def& def)
+{
+ string terr;
+ if (!get_parsed_tmpl(path_comps, false, def, terr)) {
+ output_user("%s\n", terr.c_str());
+ return false;
+ }
+ return true;
+}
+
+/* check if specified "logical path" is valid for "activate" operation
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateActivatePath(const vector<string>& path_comps)
+{
+ vtw_def def;
+ if (!validate_act_deact(path_comps, "activate", def)) {
+ return false;
+ }
+ if (!cfgPathMarkedDeactivated(path_comps)) {
+ output_user("Activate can only be performed on a node on which the "
+ "deactivate\ncommand has been performed.\n");
+ return false;
+ }
+ bool ret = true;
+ if (def.is_value && def.tag && def.def_tag > 0) {
+ // we are activating a tag, and there is a limit on number of tags.
+ vector<string> 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<string>& path_comps)
+{
+ vtw_def def;
+ return validate_act_deact(path_comps, "deactivate", def);
+}
+
+/* check if specified "logical path" is valid for "edit" operation.
+ * return false if invalid.
+ * if valid, set "env" arg to the environment string needed for the "edit"
+ * operation and return true.
+ */
+bool
+Cstore::getEditEnv(const vector<string>& path_comps, string& env)
+{
+ vtw_def def;
+ string terr;
+ if (!get_parsed_tmpl(path_comps, false, def, terr)) {
+ output_user("%s\n", terr.c_str());
+ return false;
+ }
+ if (!cfg_path_exists(path_comps, false, true)) {
+ output_user("The specified config path does not exist\n");
+ return false;
+ }
+ /* "edit" is only allowed when path ends at a
+ * (1) "tag value"
+ * OR
+ * (2) "typeless node"
+ */
+ if (!(def.is_value && def.tag)
+ && !(!def.is_value && def.def_type == ERROR_TYPE)) {
+ // neither "tag value" nor "typeless node"
+ output_user("The \"edit\" command cannot be issued "
+ "at the specified level\n");
+ return false;
+ }
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ append_tmpl_path(path_comps);
+ get_edit_env(env);
+ RESTORE_PATHS;
+ /* doing the save/restore above to be consistent with the rest of the API.
+ * however, after the caller evals the returned environment string, the
+ * levels in "this" will become out-of-sync with the environment. so
+ * "this" should no longer be used and a new object should be created.
+ *
+ * this is only an issue if the calling process doesn't terminate. since
+ * the function should only be used by the shell/completion, it's not a
+ * problem (each invocation of my_cli_shell_api uses a new object anyway).
+ */
+
+ return true;
+}
+
+/* set "env" arg to the environment string needed for the "up" operation.
+ * return true if successful.
+ */
+bool
+Cstore::getEditUpEnv(string& env)
+{
+ /* "up" is based on current levels in environment. levels should already
+ * be set up in constructor (with "use_edit_level" true).
+ */
+ if (edit_level_at_root()) {
+ output_user("Already at the top level\n");
+ return false;
+ }
+
+ /* get_parsed_tmpl() does not allow empty path, so use one component
+ * from current paths.
+ */
+ vtw_def def;
+ string terr;
+ vector<string> path_comps;
+ if (!get_parsed_tmpl(path_comps, false, def, terr)) {
+ // this should not happen since it's using existing levels
+ output_user("%s\n", terr.c_str());
+ return false;
+ }
+ SAVE_PATHS;
+ if (def.is_value && def.tag) {
+ // edit level is at "tag value". go up 1 extra level.
+ pop_cfg_path();
+ pop_tmpl_path();
+ }
+ pop_cfg_path();
+ pop_tmpl_path();
+ get_edit_env(env);
+ RESTORE_PATHS;
+ // also see getEditEnv for comment on save/restore above
+
+ return true;
+}
+
+/* set "env" arg to the environment string needed to reset edit levels.
+ * return true if successful.
+ */
+bool
+Cstore::getEditResetEnv(string& env)
+{
+ SAVE_PATHS;
+ while (!edit_level_at_root()) {
+ pop_cfg_path();
+ pop_tmpl_path();
+ }
+ get_edit_env(env);
+ RESTORE_PATHS;
+ // also see getEditEnv for comment on save/restore above
+
+ return true;
+}
+
+/* set "env" arg to the environment string needed for "completion".
+ * return true if successful.
+ *
+ * note: comps must have at least 2 components, the "command" and the
+ * first path element (which can be empty string).
+ */
+bool
+Cstore::getCompletionEnv(const vector<string>& comps, string& env)
+{
+ vector<string> pcomps = comps;
+ string cmd = pcomps[0];
+ string last_comp = pcomps.back();
+ pcomps.erase(pcomps.begin());
+ pcomps.pop_back();
+ bool exists_only = (cmd == "delete" || cmd == "show" || cmd == "edit"
+ || cmd == "comment" || cmd == "activate"
+ || cmd == "deactivate");
+
+ /* at this point, pcomps contains the command line arguments minus the
+ * "command" and the last one.
+ */
+ bool ret = false;
+ SAVE_PATHS;
+ do {
+ vtw_def def;
+ if (pcomps.size() > 0) {
+ if (!get_parsed_tmpl(pcomps, false, def)) {
+ // invalid path
+ break;
+ }
+ if (exists_only && !cfg_path_exists(pcomps, false, true)) {
+ // invalid path for the command (must exist)
+ break;
+ }
+ append_cfg_path(pcomps);
+ append_tmpl_path(pcomps);
+ } else {
+ // we are at root. simulate a typeless node.
+ def.def_type = ERROR_TYPE;
+ def.tag = def.multi = def.is_value = 0;
+ }
+
+ /* at this point, cfg and tmpl paths are constructed up to the comp
+ * before last_comp, and def is parsed.
+ */
+ if (def.is_value && !def.tag) {
+ // invalid path (this means the comp before last_comp is a leaf value)
+ break;
+ }
+
+ vector<string> comp_vals;
+ string comp_string;
+ string comp_help;
+ vector<pair<string, string> > 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<string> ufvec;
+ if (exists_only) {
+ // only return existing config nodes
+ get_all_child_node_names(ufvec, false, true);
+ } else {
+ // return all template children
+ get_all_tmpl_child_node_names(ufvec);
+ }
+ for (unsigned int i = 0; i < ufvec.size(); i++) {
+ if (last_comp == ""
+ || ufvec[i].compare(0, last_comp.length(), last_comp) == 0) {
+ comp_vals.push_back(ufvec[i]);
+ }
+ }
+ if (comp_vals.size() == 0) {
+ // no matches
+ break;
+ }
+ sort(comp_vals.begin(), comp_vals.end());
+ for (unsigned int i = 0; i < comp_vals.size(); i++) {
+ pair<string, string> hpair(comp_vals[i], "");
+ push_tmpl_path(hpair.first);
+ vtw_def cdef;
+ if (tmpl_parse(cdef)) {
+ hpair.second = cdef.def_node_help;
+ } else {
+ hpair.second = "<No help text available>";
+ }
+ help_pairs.push_back(hpair);
+ pop_tmpl_path();
+ }
+ // last comp is not value
+ last_comp_val = false;
+ } else {
+ /* path so far is at a "value node".
+ * note: follow the original implementation and don't filter
+ * non-existent values for such completions
+ */
+ // first, handle completions.
+ if (def.tag) {
+ // it's a "tag node". get completions from tag values.
+ get_all_child_node_names(comp_vals, false, true);
+ } else {
+ // it's a "leaf value node". get completions from values.
+ read_value_vec(comp_vals, false);
+ }
+ /* more possible completions from this node's template:
+ * "allowed"
+ * "enumeration"
+ * "$VAR(@) in ..."
+ */
+ if (def.def_enumeration || def.def_allowed) {
+ /* do "enumeration" or "allowed".
+ * note: emulate original implementation and set up COMP_WORDS and
+ * COMP_CWORD environment variables. these are needed by some
+ * "allowed" scripts.
+ */
+ ostringstream cword_count;
+ cword_count << (comps.size() - 1);
+ string cmd_str = ("export " + C_ENV_SHELL_CWORD_COUNT + "="
+ + cword_count.str() + "; ");
+ cmd_str += ("export " + C_ENV_SHELL_CWORDS + "=(");
+ for (unsigned int i = 0; i < comps.size(); i++) {
+ cmd_str += (" '" + comps[i] + "'");
+ }
+ cmd_str += "); ";
+ if (def.def_enumeration) {
+ cmd_str += (C_ENUM_SCRIPT_DIR + "/" + def.def_enumeration);
+ } else {
+ cmd_str += def.def_allowed;
+ }
+
+ char *buf = (char *) malloc(MAX_CMD_OUTPUT_SIZE);
+ int ret = get_shell_command_output(cmd_str.c_str(), buf,
+ MAX_CMD_OUTPUT_SIZE);
+ if (ret > 0) {
+ // '<' and '>' need to be escaped
+ char *ptr = buf;
+ while (*ptr) {
+ if (*ptr == '<' || *ptr == '>') {
+ comp_string += "\\";
+ }
+ comp_string += *ptr;
+ ptr++;
+ }
+ }
+ /* note that for "enumeration" and "allowed", comp_string is the
+ * complete output of the command and it is to be evaled by the
+ * shell into an array of values.
+ */
+ free(buf);
+ } else if (def.actions[syntax_act].vtw_list_head) {
+ // look for "self ref in values" from syntax
+ const valstruct *vals = get_syntax_self_in_valstruct(
+ def.actions[syntax_act].vtw_list_head);
+ if (vals) {
+ if (vals->cnt == 0 && vals->val) {
+ comp_vals.push_back(vals->val);
+ } else if (vals->cnt > 0) {
+ for (int i = 0; i < vals->cnt; i++) {
+ if (vals->vals[i]) {
+ comp_vals.push_back(vals->vals[i]);
+ }
+ }
+ }
+ }
+ }
+
+ // now handle help.
+ if (def.def_comp_help) {
+ // "comp_help" exists.
+ comp_help = def.def_comp_help;
+ shell_escape_squotes(comp_help);
+ }
+ if (def.def_val_help) {
+ // has val_help. first separate individual lines.
+ unsigned int start = 0, i = 0;
+ vector<string> 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<string, string>(
+ 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<string, string>(
+ type_to_name(def.def_type2), vhelps[i]));
+ }
+ } else {
+ // ';' at index sc
+ help_pairs.push_back(pair<string, string>(
+ 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<string, string>(type_to_name(def.def_type),
+ def.def_node_help));
+ }
+ }
+
+ // this var is the array of possible completions
+ env = (C_ENV_SHAPI_COMP_VALS + "=(");
+ for (unsigned int i = 0; i < comp_vals.size(); i++) {
+ shell_escape_squotes(comp_vals[i]);
+ env += ("'" + comp_vals[i] + "' ");
+ }
+ /* as mentioned above, comp_string is the complete command output.
+ * let the shell eval it into the array since we don't want to
+ * re-implement the shell interpretation here.
+ *
+ * note that as a result, we will not be doing the filtering here.
+ * instead, the completion script will do the filtering on
+ * the resulting comp_values array. should be straightforward since
+ * there's no "existence filtering", only "prefix filtering".
+ */
+ env += (comp_string + "); ");
+
+ /* this var indicates whether the last comp is "value"
+ * follow original implementation: if last comp is value, completion
+ * script needs to do the following.
+ * use comp_help if exists
+ * prefix filter comp_values
+ * replace any <*> in comp_values with ""
+ * convert help items to data representation
+ */
+ env += (C_ENV_SHAPI_LCOMP_VAL + "=");
+ env += (last_comp_val ? "true; " : "false; ");
+
+ // this var is the "comp_help" string
+ env += (C_ENV_SHAPI_COMP_HELP + "='" + comp_help + "'; ");
+
+ // this var is the array of "help items", i.e., type names, etc.
+ string hitems = (C_ENV_SHAPI_HELP_ITEMS + "=(");
+ // this var is the array of "help strings" corresponding to the items
+ string hstrs = (C_ENV_SHAPI_HELP_STRS + "=(");
+ for (unsigned int i = 0; i < help_pairs.size(); i++) {
+ string hi = help_pairs[i].first;
+ string hs = help_pairs[i].second;
+ shell_escape_squotes(hi);
+ shell_escape_squotes(hs);
+ // get rid of leading/trailing "space" chars in help string
+ while (hi.size() > 0 && isspace(hi[0])) {
+ hi.erase(0, 1);
+ }
+ while (hs.size() > 0 && isspace(hs[0])) {
+ hs.erase(0, 1);
+ }
+ while (hi.size() > 0 && isspace(hi[hi.size() - 1])) {
+ hi.erase(hi.size() - 1);
+ }
+ while (hs.size() > 0 && isspace(hs[hs.size() - 1])) {
+ hs.erase(hs.size() - 1);
+ }
+ hitems += ("'" + hi + "' ");
+ hstrs += ("'" + hs + "' ");
+ }
+ env += (hitems + "); " + hstrs + "); ");
+ ret = true;
+ } while(0);
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* set specified "logical path" in "working config".
+ * return true if successful. otherwise return false.
+ * note: assume specified path is valid (i.e., validateSetPath()).
+ */
+bool
+Cstore::setCfgPath(const vector<string>& path_comps)
+{
+ vector<string> ppath;
+ vtw_def def;
+ bool ret = true;
+ bool path_exists = true;
+ // do the set from the top down
+ SAVE_PATHS;
+ for (unsigned int i = 0; i < path_comps.size(); i++) {
+ // partial path
+ ppath.push_back(path_comps[i]);
+
+ // get template at this level
+ if (!get_parsed_tmpl(ppath, false, def)) {
+ output_user("paths[%s,%s]\n", cfg_path_to_str().c_str(),
+ tmpl_path_to_str().c_str());
+ for (unsigned int i = 0; i < ppath.size(); i++) {
+ output_user(" [%s]\n", ppath[i].c_str());
+ }
+ exit_internal("failed to get tmpl during set. not validate first?\n");
+ }
+
+ // nop if this level already in working (including deactivated)
+ if (cfg_path_exists(ppath, false, true)) {
+ continue;
+ }
+ path_exists = false;
+
+ // this level not in working. set it.
+ append_cfg_path(ppath);
+ append_tmpl_path(ppath);
+
+ if (!def.is_value) {
+ // this level is a "node"
+ if (!add_node() || !create_default_children()) {
+ ret = false;
+ break;
+ }
+ } else if (def.tag) {
+ // this level is a "tag value".
+ // add the tag, taking the max tag limit into consideration.
+ if (!add_tag(def) || !create_default_children()) {
+ ret = false;
+ break;
+ }
+ } else {
+ // this level is a "value" of a single-/multi-value node.
+ // go up 1 level to get the node.
+ pop_cfg_path();
+ if (def.multi) {
+ // value of multi-value node.
+ // add the value, taking the max multi limit into consideration.
+ if (!add_value_to_multi(def, ppath.back())) {
+ ret = false;
+ break;
+ }
+ } else {
+ // value of single-value node
+ if (!write_value(ppath.back())) {
+ ret = false;
+ break;
+ }
+ }
+ }
+ RESTORE_PATHS;
+ }
+ RESTORE_PATHS; // if "break" was hit
+
+ if (ret && def.is_value && def.def_default) {
+ /* a node with default has been explicitly set. needs to be marked
+ * as non-default for display purposes.
+ *
+ * note: when the new value is the same as the old value, the behavior
+ * is different from before, which was a nop. the new behavior is
+ * that the value will remain unchanged, but the "default status"
+ * will be changed, i.e., it will be marked as non-default.
+ */
+ append_cfg_path(path_comps);
+ pop_cfg_path();
+ // only do it if it's previously marked default
+ if (marked_display_default()) {
+ ret = unmark_display_default();
+
+ /* XXX work around current commit's unionfs implementation problem.
+ * current commit's unionfs implementation looks at the "changes only"
+ * directory (i.e., the r/w portion of the union mount), which is wrong.
+ *
+ * all config information should be obtained from two directories:
+ * "active" and "working", e.g., instead of looking at whiteout files
+ * in "changes only" to find deleted nodes, nodes that are in "active"
+ * but not in "working" have been deleted.
+ *
+ * in this particular case, commit looks at "changes only" to read the
+ * node.val file. however, since the value didn't change (only the
+ * "default status" changed), node.val doesn't appear in "changes only".
+ * here we re-write the file to force it into "changes only" so that
+ * commit can work correctly.
+ */
+ vector<string> vvec;
+ read_value_vec(vvec, false);
+ write_value_vec(vvec);
+
+ // pretend it didn't exist since we changed the status
+ path_exists = false;
+ }
+ RESTORE_PATHS;
+ }
+ if (path_exists) {
+ // whole path already exists
+ output_user("The specified configuration node already exists\n");
+ // treat as success
+ }
+ return ret;
+}
+
+/* check if specified "arguments" is valid for "rename" operation
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateRenameArgs(const vector<string>& args)
+{
+ return validate_rename_copy(args, "rename");
+}
+
+/* check if specified "arguments" is valid for "copy" operation
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateCopyArgs(const vector<string>& args)
+{
+ return validate_rename_copy(args, "copy");
+}
+
+/* check if specified "arguments" is valid for "move" operation
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validateMoveArgs(const vector<string>& args)
+{
+ vector<string> epath;
+ vector<string> 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<string>& args, vtw_def& def)
+{
+ /* separate path from comment.
+ * follow the original implementation: the last arg is the comment, and
+ * everything else is part of the path.
+ */
+ vector<string> 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<string>& args)
+{
+ string otagnode = args[0];
+ string otagval = args[1];
+ string ntagval = args[4];
+ push_cfg_path(otagnode);
+ bool ret = rename_child_node(otagval, ntagval);
+ pop_cfg_path();
+ return ret;
+}
+
+/* perform copy in "working config" according to specified args.
+ * return true if successful. otherwise return false.
+ * note: assume args are already validated (i.e., validateCopyArgs()).
+ */
+bool
+Cstore::copyCfgPath(const vector<string>& args)
+{
+ string otagnode = args[0];
+ string otagval = args[1];
+ string ntagval = args[4];
+ push_cfg_path(otagnode);
+ bool ret = copy_child_node(otagval, ntagval);
+ pop_cfg_path();
+ return ret;
+}
+
+/* perform "comment" in working config according to specified args.
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::commentCfgPath(const vector<string>& args, const vtw_def& def)
+{
+ // separate path from comment
+ vector<string> path_comps(args);
+ string comment = args.back();
+ path_comps.pop_back();
+
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret;
+ if (comment == "") {
+ // follow original impl: empty comment => remove it
+ ret = remove_comment();
+ if (!ret) {
+ output_user("Failed to remove comment for specified config node\n");
+ }
+ } else {
+ ret = set_comment(comment);
+ if (!ret) {
+ output_user("Failed to add comment for specified config node\n");
+ }
+ }
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* discard all changes in working config.
+ * return true if successful. otherwise return false.
+ */
+bool
+Cstore::discardChanges()
+{
+ // just call underlying implementation
+ unsigned long long num_removed = 0;
+ if (discard_changes(num_removed)) {
+ if (num_removed > 0) {
+ output_user("Changes have been discarded\n");
+ } else {
+ output_user("No changes have been discarded\n");
+ }
+ return true;
+ }
+ return false;
+}
+
+/* perform move in "working config" according to specified args.
+ * return true if successful. otherwise return false.
+ * note: assume args are already validated (i.e., validateMoveArgs()).
+ */
+bool
+Cstore::moveCfgPath(const vector<string>& args)
+{
+ vector<string> epath;
+ vector<string> 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<string>& path_comps, bool active_cfg)
+{
+ return cfg_path_exists(path_comps, active_cfg, false);
+}
+
+// same as above but "deactivate-aware"
+bool
+Cstore::cfgPathExistsDA(const vector<string>& path_comps, bool active_cfg,
+ bool include_deactivated)
+{
+ return cfg_path_exists(path_comps, active_cfg, include_deactivated);
+}
+
+/* check if specified "logical path" has been deleted in working config.
+ */
+bool
+Cstore::cfgPathDeleted(const vector<string>& path_comps)
+{
+ // whether it's in active but not in working
+ return (cfg_path_exists(path_comps, true, false)
+ && !cfg_path_exists(path_comps, false, false));
+}
+
+/* check if specified "logical path" has been added in working config.
+ */
+bool
+Cstore::cfgPathAdded(const vector<string>& path_comps)
+{
+ // whether it's not in active but in working
+ return (!cfg_path_exists(path_comps, true, false)
+ && cfg_path_exists(path_comps, false, false));
+}
+
+/* check if specified "logical path" has been "changed" in working config.
+ * XXX the definition of "changed" is different from the original
+ * perl API implementation isChanged(), which was inconsistent between
+ * "deleted" and "deactivated".
+ *
+ * original logic (with $disable arg not defined) returns true in
+ * either of the 2 cases below:
+ * (1) node is BEING deactivated or activated
+ * (2) node appears in changes_only dir
+ * which means it returns false for nodes being deleted but true
+ * for nodes being deactivated.
+ *
+ * new logic returns true if any of the following is true
+ * (remember this functions is NOT "deactivate-aware")
+ * (1) cfgPathDeleted()
+ * (2) cfgPathAdded()
+ * (3) marked_changed()
+ */
+bool
+Cstore::cfgPathChanged(const vector<string>& path_comps)
+{
+ if (cfgPathDeleted(path_comps) || cfgPathAdded(path_comps)) {
+ return true;
+ }
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret = marked_changed();
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* get names of "deleted" child nodes of specified path during commit
+ * operation. names are returned in cnodes.
+ */
+void
+Cstore::cfgPathGetDeletedChildNodes(const vector<string>& path_comps,
+ vector<string>& cnodes)
+{
+ cfgPathGetDeletedChildNodesDA(path_comps, cnodes, false);
+}
+
+// same as above but "deactivate-aware"
+void
+Cstore::cfgPathGetDeletedChildNodesDA(const vector<string>& path_comps,
+ vector<string>& cnodes,
+ bool include_deactivated)
+{
+ vector<string> acnodes;
+ cfgPathGetChildNodesDA(path_comps, acnodes, true, include_deactivated);
+ vector<string> wcnodes;
+ cfgPathGetChildNodesDA(path_comps, wcnodes, false, include_deactivated);
+ map<string, bool> cmap;
+ for (unsigned int i = 0; i < wcnodes.size(); i++) {
+ cmap[wcnodes[i]] = true;
+ }
+ for (unsigned int i = 0; i < acnodes.size(); i++) {
+ if (cmap.find(acnodes[i]) == cmap.end()) {
+ // in active but not in working
+ cnodes.push_back(acnodes[i]);
+ }
+ }
+}
+
+/* this is the equivalent of the listNodeStatus() from the original
+ * perl API. it provides the "status" ("deleted", "added", "changed",
+ * or "static") of each child node of specified path.
+ * cmap: (output) contains the status of child nodes.
+ *
+ * note: this function is NOT "deactivate-aware".
+ */
+void
+Cstore::cfgPathGetChildNodesStatus(const vector<string>& path_comps,
+ map<string, string>& cmap)
+{
+ // get a union of active and working
+ map<string, bool> umap;
+ vector<string> acnodes;
+ vector<string> wcnodes;
+ cfgPathGetChildNodes(path_comps, acnodes, true);
+ cfgPathGetChildNodes(path_comps, wcnodes, false);
+ for (unsigned int i = 0; i < acnodes.size(); i++) {
+ umap[acnodes[i]] = true;
+ }
+ for (unsigned int i = 0; i < wcnodes.size(); i++) {
+ umap[wcnodes[i]] = true;
+ }
+
+ // get the status of each one
+ vector<string> ppath = path_comps;
+ map<string, bool>::iterator it = umap.begin();
+ for (; it != umap.end(); ++it) {
+ string c = (*it).first;
+ ppath.push_back(c);
+ // note: "changed" includes "deleted" and "added", so check those first.
+ if (cfgPathDeleted(ppath)) {
+ cmap[c] = C_NODE_STATUS_DELETED;
+ } else if (cfgPathAdded(ppath)) {
+ cmap[c] = C_NODE_STATUS_ADDED;
+ } else if (cfgPathChanged(ppath)) {
+ cmap[c] = C_NODE_STATUS_CHANGED;
+ } else {
+ cmap[c] = C_NODE_STATUS_STATIC;
+ }
+ ppath.pop_back();
+ }
+}
+
+/* DA version of the above function.
+ * cmap: (output) contains the status of child nodes.
+ *
+ * note: this follows the original perl API listNodeStatus() implementation.
+ */
+void
+Cstore::cfgPathGetChildNodesStatusDA(const vector<string>& path_comps,
+ map<string, string>& cmap)
+{
+ // process deleted nodes first
+ vector<string> del_nodes;
+ cfgPathGetDeletedChildNodesDA(path_comps, del_nodes);
+ for (unsigned int i = 0; i < del_nodes.size(); i++) {
+ cmap[del_nodes[i]] = C_NODE_STATUS_DELETED;
+ }
+
+ // get all nodes in working config
+ vector<string> work_nodes;
+ cfgPathGetChildNodesDA(path_comps, work_nodes, false);
+ vector<string> ppath = path_comps;
+ for (unsigned int i = 0; i < work_nodes.size(); i++) {
+ ppath.push_back(work_nodes[i]);
+ /* note: in the DA version here, we do NOT check the deactivate state
+ * when considering the state of the child nodes (which include
+ * deactivated ones). the reason is that this DA function is used
+ * for config output-related operations and should return whether
+ * each node is actually added/deleted from the config independent
+ * of its deactivate state.
+ *
+ * for "added" state, can't use cfgPathAdded() since it's not DA.
+ *
+ * deleted ones already handled above.
+ */
+ if (!cfg_path_exists(ppath, true, true)
+ && cfg_path_exists(ppath, false, true)) {
+ cmap[work_nodes[i]] = C_NODE_STATUS_ADDED;
+ } else if (cfgPathChanged(ppath)) {
+ cmap[work_nodes[i]] = C_NODE_STATUS_CHANGED;
+ } else {
+ cmap[work_nodes[i]] = C_NODE_STATUS_STATIC;
+ }
+ ppath.pop_back();
+ }
+}
+
+/* check whether specified path is "deactivated" in working config or
+ * active config.
+ * a node is "deactivated" if the node itself or any of its ancestors is
+ * "marked deactivated".
+ */
+bool
+Cstore::cfgPathDeactivated(const vector<string>& path_comps, bool active_cfg)
+{
+ vector<string> ppath;
+ for (unsigned int i = 0; i < path_comps.size(); i++) {
+ ppath.push_back(path_comps[i]);
+ if (cfgPathMarkedDeactivated(ppath, active_cfg)) {
+ // an ancestor or itself is marked deactivated
+ return true;
+ }
+ }
+ return false;
+}
+
+/* check whether specified path is "marked deactivated" in working config or
+ * active config.
+ * a node is "marked deactivated" if a deactivate operation has been
+ * performed on the node.
+ */
+bool
+Cstore::cfgPathMarkedDeactivated(const vector<string>& path_comps,
+ bool active_cfg)
+{
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret = marked_deactivated(active_cfg);
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* get names of child nodes of specified path in working config or active
+ * config. names are returned in cnodes.
+ */
+void
+Cstore::cfgPathGetChildNodes(const vector<string>& path_comps,
+ vector<string>& cnodes, bool active_cfg)
+{
+ cfgPathGetChildNodesDA(path_comps, cnodes, active_cfg, false);
+}
+
+// same as above but "deactivate-aware"
+void
+Cstore::cfgPathGetChildNodesDA(const vector<string>& path_comps,
+ vector<string>& cnodes, bool active_cfg,
+ bool include_deactivated)
+{
+ if (!include_deactivated && cfgPathDeactivated(path_comps, active_cfg)) {
+ /* this node is deactivated (an ancestor or this node itself is
+ * marked deactivated) and we don't want to include deactivated. nop.
+ */
+ return;
+ }
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ get_all_child_node_names(cnodes, active_cfg, include_deactivated);
+ RESTORE_PATHS;
+}
+
+/* get value of specified single-value node.
+ * value: (output) node value.
+ * active_cfg: whether to get value from active config.
+ * return false if fails (invalid node, doesn't exist, read fails, etc.).
+ * otherwise return true.
+ */
+bool
+Cstore::cfgPathGetValue(const vector<string>& path_comps, string& value,
+ bool active_cfg)
+{
+ return cfgPathGetValueDA(path_comps, value, active_cfg, false);
+}
+
+// same as above but "deactivate-aware"
+bool
+Cstore::cfgPathGetValueDA(const vector<string>& path_comps, string& value,
+ bool active_cfg, bool include_deactivated)
+{
+ vtw_def def;
+ if (!get_parsed_tmpl(path_comps, false, def)) {
+ // invalid node
+ return false;
+ }
+ /* note: the behavior here is different from original perl API, which
+ * does not check if specified node is indeed single-value. so if
+ * the function is erroneously used on a multi-value node, the
+ * original API will return a single string that includes all values.
+ * this new function will return failure in such cases.
+ */
+ if (def.is_value || def.multi || def.tag || def.def_type == ERROR_TYPE) {
+ // specified path is not a single-value node
+ return false;
+ }
+ if (!cfg_path_exists(path_comps, active_cfg, include_deactivated)) {
+ // specified node doesn't exist
+ return false;
+ }
+ vector<string> 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<string>& path_comps,
+ vector<string>& values, bool active_cfg)
+{
+ return cfgPathGetValuesDA(path_comps, values, active_cfg, false);
+}
+
+// same as above but "deactivate-aware"
+bool
+Cstore::cfgPathGetValuesDA(const vector<string>& path_comps,
+ vector<string>& values, bool active_cfg,
+ bool include_deactivated)
+{
+ vtw_def def;
+ if (!get_parsed_tmpl(path_comps, false, def)) {
+ // invalid node
+ return false;
+ }
+ /* note: the behavior here is different from original perl API, which
+ * does not check if specified node is indeed multi-value. so if
+ * the function is erroneously used on a single-value node, the
+ * original API will return the node's value. this new function
+ * will return failure in such cases.
+ */
+ if (def.is_value || !def.multi || def.tag || def.def_type == ERROR_TYPE) {
+ // specified path is not a multi-value node
+ return false;
+ }
+ if (!cfg_path_exists(path_comps, active_cfg, include_deactivated)) {
+ // specified node doesn't exist
+ return false;
+ }
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret = read_value_vec(values, active_cfg);
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* get comment of specified node.
+ * comment: (output) node comment.
+ * active_cfg: whether to get comment from active config.
+ * return false if fails (invalid node, doesn't exist, etc.).
+ * otherwise return true.
+ */
+bool
+Cstore::cfgPathGetComment(const vector<string>& path_comps, string& comment,
+ bool active_cfg)
+{
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret = get_comment(comment, active_cfg);
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* the following functions are observers of the "effective" config.
+ * they can be used
+ * (1) outside a config session (e.g., op mode, daemons, callbacks, etc.).
+ * OR
+ * (2) during a config session
+ *
+ * HOWEVER, NOTE that the definition of "effective" is different under these
+ * two scenarios.
+ * (1) when used outside a config session, "effective" == "active".
+ * in other words, in such cases the effective config is the same
+ * as the running config.
+ *
+ * (2) when used during a config session, a config path (leading to either
+ * a "node" or a "value") is "effective" if ANY of the following
+ * is true.
+ * (a) active && working
+ * path is in both active and working configs, i.e., unchanged.
+ * (b) !active && working && committed
+ * path is not in active, has been set in working, AND has
+ * already been committed, i.e., "commit" has successfully
+ * processed the addition/update of the path.
+ * (c) active && !working && !committed
+ * path is in active, has been deleted from working, AND
+ * has not been committed yet, i.e., "commit" (per priority) has
+ * not processed the deletion of the path yet, or it has been
+ * processed but failed.
+ *
+ * note: during commit, deactivate has the same effect as delete. so
+ * in such cases, as far as these functions are concerned,
+ * deactivated nodes don't exist.
+ *
+ * originally, these functions are exclusively for use during config
+ * sessions. however, for some usage scenarios, it is useful to have a set
+ * of API functions that can be used both during and outside config
+ * sessions. therefore, definition (1) is added above for convenience.
+ *
+ * for example, a developer can use these functions in a script that can
+ * be used both during a commit action and outside config mode, as long as
+ * the developer is clearly aware of the difference between the above two
+ * definitions.
+ *
+ * note that when used outside a config session (i.e., definition (1)),
+ * these functions are equivalent to the observers for the "active" config.
+ *
+ * to avoid any confusiton, when possible (e.g., in a script that is
+ * exclusively used in op mode), developers should probably use those
+ * "active" observers explicitly when outside a config session instead
+ * of these "effective" observers.
+ *
+ * it is also important to note that when used outside a config session,
+ * due to race conditions, it is possible that the "observed" active config
+ * becomes out-of-sync with the config that is actually "in effect".
+ * specifically, this happens when two things occur simultaneously:
+ * (a) an observer function is called from outside a config session.
+ * AND
+ * (b) someone invokes "commit" inside a config session (any session).
+ *
+ * this is because "commit" only updates the active config at the end after
+ * all commit actions have been executed, so before the update happens,
+ * some config nodes have already become "effective" but are not yet in the
+ * "active config" and therefore are not observed by these functions.
+ *
+ * note that this is only a problem when the caller is outside config mode.
+ * in such cases, the caller (which could be an op-mode command, a daemon,
+ * a callback script, etc.) already must be able to handle config changes
+ * that can happen at any time. if "what's configured" is more important,
+ * using the "active config" should be fine as long as it is relatively
+ * up-to-date. if the actual "system state" is more important, then the
+ * caller should probably just check the system state in the first place
+ * (instead of using these config observers).
+ *
+ * one possible solution is for these "effective" observers to obtain the
+ * global commit lock before returning their observations. this has not
+ * been implemented yet since the impact of this issue is not clear at
+ * the moment.
+ */
+
+// return whether specified path is "effective".
+bool
+Cstore::cfgPathEffective(const vector<string>& 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<string>& path_comps,
+ vector<string>& cnodes)
+{
+ if (!inSession()) {
+ // not in a config session. use active config only.
+ cfgPathGetChildNodes(path_comps, cnodes, true);
+ return;
+ }
+
+ // get a union of active and working
+ map<string, bool> cmap;
+ vector<string> acnodes;
+ vector<string> wcnodes;
+ cfgPathGetChildNodes(path_comps, acnodes, true);
+ cfgPathGetChildNodes(path_comps, wcnodes, false);
+ for (unsigned int i = 0; i < acnodes.size(); i++) {
+ cmap[acnodes[i]] = true;
+ }
+ for (unsigned int i = 0; i < wcnodes.size(); i++) {
+ cmap[wcnodes[i]] = true;
+ }
+
+ // get only the effective ones from the union
+ vector<string> ppath = path_comps;
+ map<string, bool>::iterator it = cmap.begin();
+ for (; it != cmap.end(); ++it) {
+ string c = (*it).first;
+ ppath.push_back(c);
+ if (cfgPathEffective(ppath)) {
+ cnodes.push_back(c);
+ }
+ ppath.pop_back();
+ }
+}
+
+/* get the "effective" value of specified path during commit operation.
+ * value: (output) node value
+ * return true if successful. otherwise return false.
+ */
+bool
+Cstore::cfgPathGetEffectiveValue(const vector<string>& path_comps,
+ string& value)
+{
+ if (!inSession()) {
+ // not in a config session. use active config only.
+ return cfgPathGetValue(path_comps, value, true);
+ }
+
+ vector<string> 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<string>& path_comps,
+ vector<string>& values)
+{
+ if (!inSession()) {
+ // not in a config session. use active config only.
+ cfgPathGetValues(path_comps, values, true);
+ return (values.size() > 0);
+ }
+
+ // get a union of active and working
+ map<string, bool> vmap;
+ vector<string> ovals;
+ vector<string> nvals;
+ cfgPathGetValues(path_comps, ovals, true);
+ cfgPathGetValues(path_comps, nvals, false);
+ for (unsigned int i = 0; i < ovals.size(); i++) {
+ vmap[ovals[i]] = true;
+ }
+ for (unsigned int i = 0; i < nvals.size(); i++) {
+ vmap[nvals[i]] = true;
+ }
+
+ // get only the effective ones from the union
+ vector<string> ppath = path_comps;
+ map<string, bool>::iterator it = vmap.begin();
+ for (; it != vmap.end(); ++it) {
+ string c = (*it).first;
+ ppath.push_back(c);
+ if (cfgPathEffective(ppath)) {
+ values.push_back(c);
+ }
+ ppath.pop_back();
+ }
+ return (values.size() > 0);
+}
+
+/* get the value string that corresponds to specified variable ref string.
+ * ref_str: var ref string (e.g., "./cost/@").
+ * cval: (output) contains the resulting string.
+ * from_active: if true, value string should come from "active config".
+ * otherwise from "working config".
+ * return true if successful. otherwise return false.
+ */
+bool
+Cstore::getVarRef(const string& ref_str, clind_val& cval, bool from_active)
+{
+ bool ret = true;
+ SAVE_PATHS;
+ VarRef vref(this, ref_str, from_active);
+ string val;
+ vtw_type_e type;
+ if (!vref.getValue(val, type)) {
+ ret = false;
+ } else {
+ cval.val_type = type;
+ // follow original implementation. caller is supposed to free this.
+ cval.value = strdup(val.c_str());
+ }
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* set the node corresponding to specified variable ref string to specified
+ * value.
+ * ref_str: var ref string (e.g., "../encrypted-password/@").
+ * value: value to be set.
+ * to_active: if true, set in "active config".
+ * otherwise in "working config".
+ * return true if successful. otherwise return false.
+ */
+bool
+Cstore::setVarRef(const string& ref_str, const string& value, bool to_active)
+{
+ /* XXX functions in cli_new only performs "set var ref" operations (e.g.,
+ * '$VAR(@) = ""', which sets current node's value to empty string)
+ * during "commit", i.e., if a "set var ref" is specified in
+ * "syntax:", it will not be performed during "set" (but will be
+ * during commit).
+ *
+ * since commit has not been converted to use the new library, it
+ * does not use this function. instead, it uses the "cli_val_engine"
+ * implementation (where filesystem paths are deeply embedded, which
+ * makes it difficult to abstract low-level filesystem operations
+ * from high-level functions). as a result, this function is unused
+ * and untested at the moment. must revisit when converting commit.
+ */
+ SAVE_PATHS;
+ VarRef vref(this, ref_str, to_active);
+ vector<string> 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<string>& path_comps)
+{
+ if (cfgPathDeactivated(path_comps)) {
+ output_user("The specified configuration node is already deactivated\n");
+ // treat as success
+ return true;
+ }
+
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret = (mark_deactivated() && unmark_deactivated_descendants());
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* perform activate operation on a node, i.e., make the node no longer
+ * "marked deactivated".
+ * note: assume all validations have been peformed (see activate.cpp).
+ */
+bool
+Cstore::unmarkCfgPathDeactivated(const vector<string>& path_comps)
+{
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ bool ret = unmark_deactivated();
+ RESTORE_PATHS;
+ return ret;
+}
+
+/* mark specified path as changed in working config.
+ * the marking is used during commit to check if a node has been changed.
+ * this should be done after set/delete/activate/deactivate.
+ * note: if a node is changed, all of its ancestors are also considered
+ * changed.
+ * return true if successful. otherwise return false.
+ */
+bool
+Cstore::markCfgPathChanged(const vector<string>& path_comps)
+{
+ // first mark the root changed
+ if (!mark_changed()) {
+ return false;
+ }
+
+ // now mark each level as changed
+ vector<string> ppath;
+ for (unsigned int i = 0; i < path_comps.size(); i++) {
+ ppath.push_back(path_comps[i]);
+ if (!cfg_path_exists(ppath, false, false)) {
+ // this level no longer in working. nothing further.
+ break;
+ }
+ SAVE_PATHS;
+ append_cfg_path(ppath);
+ bool ret = mark_changed();
+ RESTORE_PATHS;
+ if (!ret) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+////// protected functions
+void
+Cstore::output_user(const char *fmt, ...)
+{
+ va_list alist;
+ va_start(alist, fmt);
+ if (out_stream) {
+ vfprintf(out_stream, fmt, alist);
+ } else {
+ vprintf(fmt, alist);
+ }
+ va_end(alist);
+}
+
+void
+Cstore::output_internal(const char *fmt, ...)
+{
+ va_list alist;
+ va_start(alist, fmt);
+
+ int fdout = -1;
+ FILE *fout = NULL;
+ do {
+ // XXX for now use the constant from cli_val.h
+ if ((fdout = open(LOGFILE_STDOUT, O_WRONLY | O_CREAT, 0660)) == -1) {
+ break;
+ }
+ if (lseek(fdout, 0, SEEK_END) == ((off_t) -1)) {
+ break;
+ }
+ if ((fout = fdopen(fdout, "a")) == NULL) {
+ break;
+ }
+ vfprintf(fout, fmt, alist);
+ } while (0);
+ va_end(alist);
+ if (fout) {
+ fclose(fout);
+ // fdout is implicitly closed
+ } else if (fdout >= 0) {
+ close(fdout);
+ }
+}
+
+
+////// private functions
+/* try to append the logical path to template path.
+ * is_tag: (output) whether the last component is a "tag".
+ * return false if logical path is not valid. otherwise return true.
+ */
+bool
+Cstore::append_tmpl_path(const vector<string>& path_comps, bool& is_tag)
+{
+ is_tag = false;
+ for (unsigned int i = 0; i < path_comps.size(); i++) {
+ push_tmpl_path(path_comps[i]);
+ if (tmpl_node_exists()) {
+ // got exact match. continue to next component.
+ continue;
+ }
+ // not exact match. check if it's a tag.
+ pop_tmpl_path();
+ push_tmpl_path_tag();
+ if (tmpl_node_exists()) {
+ // got tag match. continue to next component.
+ if (i == (path_comps.size() - 1)) {
+ // last comp
+ is_tag = true;
+ }
+ continue;
+ }
+ // not a valid path
+ return false;
+ }
+ return true;
+}
+
+/* check whether specified "logical path" is valid template path.
+ * then template at the path is parsed.
+ * path_comps: vector of path components.
+ * validate_vals: whether to validate all "values" along specified path.
+ * def: (output) parsed template.
+ * error: (output) error message if failed.
+ * return false if invalid template path. otherwise return true.
+ * note:
+ * also, if last path component is value (i.e., def.is_value), the template
+ * parsed is actually at "full path - 1".
+ */
+bool
+Cstore::get_parsed_tmpl(const vector<string>& 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<string> *pcomps = const_cast<vector<string> *>(&path_comps);
+ vector<string> new_path_comps;
+ if (path_comps.size() < 2) {
+ new_path_comps = path_comps;
+ pcomps = &new_path_comps;
+ for (unsigned int i = 0; i < 2 && pcomps->size() < 2; i++) {
+ if (!tmpl_path_at_root()) {
+ pcomps->insert(pcomps->begin(), pop_tmpl_path());
+ }
+ }
+ }
+ bool ret = false;
+ do {
+ /* cases for template path:
+ * (1) valid path ending in "actual node", i.e., typeless node, tag node,
+ * single-value node, or multi-value node:
+ * => tmpl at full path. e.g.:
+ * typeless node: "service ssh allow-root"
+ * tag node: "interfaces ethernet"
+ * single-value node: "system gateway-address"
+ * multi-value node: "system name-server"
+ * (2) valid path ending in "value", i.e., tag (value of tag node), or
+ * value of single-/multi-value node:
+ * => tmpl at "full path - 1". e.g.:
+ * "value" of tag node: "interfaces ethernet eth0"
+ * value of single-value node: "system gateway-address 1.1.1.1"
+ * value of multi-value node: "system name-server 2.2.2.2"
+ * (3) invalid path
+ * => no tmpl
+ */
+ // first scan up to "full path - 1"
+ bool valid = true;
+ for (unsigned int i = 0; i < (pcomps->size() - 1); i++) {
+ if ((*pcomps)[i] == "") {
+ // only the last component is potentially allowed to be empty str
+ valid = false;
+ break;
+ }
+ bool is_tag;
+ if (append_tmpl_path((*pcomps)[i], is_tag)) {
+ if (is_tag && validate_vals) {
+ /* last comp is tag and want to validate value.
+ * note: validate_val() will use the current tmpl path and cfg path.
+ * so need both at the "node" level before calling it.
+ * at this point cfg path is not pushed yet so no need to
+ * pop it.
+ */
+ pop_tmpl_path();
+ if (!validate_val(NULL, (*pcomps)[i])) {
+ // invalid value
+ error = "Value validation failed";
+ valid = false;
+ break;
+ }
+ // restore tmpl path
+ append_tmpl_path((*pcomps)[i], is_tag);
+ }
+ /* cfg path is not used here but is needed by validate_val(), so
+ * need to keep it in sync with tmpl path.
+ */
+ push_cfg_path((*pcomps)[i]);
+ } else {
+ // not a valid path
+ valid = false;
+ break;
+ }
+ }
+ if (!valid) {
+ // case (3)
+ break;
+ }
+ /* we are valid up to "full path - 1". now process last path component.
+ * first, if we are case (2), we should find a "value node" at this point.
+ * note: this is only possible if there are more than 1 component. otherwise
+ * we haven't done anything yet.
+ */
+ if (pcomps->size() > 1) {
+ if (tmpl_parse(def)) {
+ if (def.tag || def.multi || def.def_type != ERROR_TYPE) {
+ // case (2). last component is "value".
+ if (validate_vals) {
+ // validate value
+ if (!validate_val(&def, (*pcomps)[pcomps->size() - 1])) {
+ // invalid value
+ error = "Value validation failed";
+ break;
+ }
+ }
+ def.is_value = 1;
+ ret = true;
+ break;
+ }
+ }
+ // if no valid template or not a value, it's not case (2) so continue.
+ }
+ // now check last component
+ if ((*pcomps)[pcomps->size() - 1] == "") {
+ // only value is potentially allowed to be empty str
+ break;
+ }
+ push_tmpl_path((*pcomps)[pcomps->size() - 1]);
+ // no need to push cfg path (only needed for validate_val())
+ if (tmpl_node_exists()) {
+ // case (1). last component is "node".
+ if (!tmpl_parse(def)) {
+ exit_internal("failed to parse tmpl [%s]\n",
+ tmpl_path_to_str().c_str());
+ }
+ def.is_value = 0;
+ ret = true;
+ break;
+ }
+ // case (3) (fall through)
+ } while (0);
+ if (validate_vals) {
+ RESTORE_PATHS;
+ } else {
+ restore_paths(not_validating);
+ }
+ return ret;
+}
+
+/* check if specified "logical path" is valid for "activate" or
+ * "deactivate" operation.
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validate_act_deact(const vector<string>& path_comps, const string& op,
+ vtw_def& def)
+{
+ string terr;
+ if (!get_parsed_tmpl(path_comps, false, def, terr)) {
+ output_user("%s\n", terr.c_str());
+ return false;
+ }
+ if (def.is_value && !def.tag) {
+ /* last component is a value of a single- or multi-value node (i.e.,
+ * a leaf value) => not allowed
+ */
+ output_user("Cannot %s a leaf configuration value\n", op.c_str());
+ return false;
+ }
+ if (!cfg_path_exists(path_comps, false, true)) {
+ output_user("Nothing to %s (the specified %s does not exist)\n",
+ op.c_str(), (!def.is_value || def.tag) ? "node" : "value");
+ return false;
+ }
+ return true;
+}
+
+/* check if specified args is valid for "rename" or "copy" operation.
+ * return true if valid. otherwise return false.
+ */
+bool
+Cstore::validate_rename_copy(const vector<string>& 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<string> 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<string>& args,
+ vector<string>& edit_path_comps,
+ vector<string>& rn_args)
+{
+ /* note:
+ * "move interfaces ethernet eth2 vif 100 to 200"
+ * is equivalent to
+ * "edit interfaces ethernet eth2" + "rename vif 100 to vif 200"
+ *
+ * set the extra levels and then just validate as rename
+ */
+ unsigned int num_args = args.size();
+ if (num_args < 4) {
+ // need at least 4 args
+ return false;
+ }
+ for (unsigned int i = 0; i < (num_args - 4); i++) {
+ edit_path_comps.push_back(args[i]);
+ }
+ rn_args.push_back(args[num_args - 4]); // vif
+ rn_args.push_back(args[num_args - 3]); // 100
+ rn_args.push_back(args[num_args - 2]); // to
+ rn_args.push_back(args[num_args - 4]); // vif
+ rn_args.push_back(args[num_args - 1]); // 200
+ return true;
+}
+
+/* check if specified "logical path" exists in working config or
+ * active config.
+ * v_def: ptr to parsed template. NULL if none.
+ * return true if it exists. otherwise return false.
+ */
+bool
+Cstore::cfg_path_exists(const vector<string>& path_comps,
+ bool active_cfg, bool include_deactivated)
+{
+ SAVE_PATHS;
+ append_cfg_path(path_comps);
+ // first check if it's a "node".
+ bool ret = cfg_node_exists(active_cfg);
+ if (!ret) {
+ // doesn't exist as a node. maybe a value?
+ pop_cfg_path();
+ ret = cfg_value_exists(path_comps[path_comps.size() - 1], active_cfg);
+ }
+ RESTORE_PATHS;
+ if (ret && !include_deactivated
+ && cfgPathDeactivated(path_comps, active_cfg)) {
+ // don't include deactivated
+ ret = false;
+ }
+ return ret;
+}
+
+/* remove tag at current work path and its subtree.
+ * if specified tag is the last one, also remove the tag node.
+ * return true if successful. otherwise return false.
+ * note: assume current work path is a tag.
+ */
+bool
+Cstore::remove_tag()
+{
+ if (!remove_node()) {
+ return false;
+ }
+
+ // go up one level and check if that was the last tag
+ bool ret = true;
+ string c = pop_cfg_path();
+ vector<string> 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<string> vvec;
+ if (!read_value_vec(vvec, false)) {
+ return false;
+ }
+
+ // remove the value
+ unsigned int bc = vvec.size();
+ vector<string> nvec(vvec.begin(), remove(vvec.begin(), vvec.end(), value));
+ unsigned int ac = nvec.size();
+
+ // sanity check
+ if (ac == bc) {
+ // nothing removed
+ return false;
+ }
+ if (ac == 0) {
+ // was the last value. remove the node.
+ return remove_node();
+ } else {
+ // write the new values out
+ return write_value_vec(nvec);
+ }
+}
+
+/* check whether specified value exists at current work path.
+ * note: assume current work path is a value node.
+ */
+bool
+Cstore::cfg_value_exists(const string& value, bool active_cfg)
+{
+ // get current values
+ vector<string> 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<string> 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<string> 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<string>& cnodes, bool active_cfg,
+ bool include_deactivated)
+{
+ vector<string> nodes;
+ get_all_child_node_names_impl(nodes, active_cfg);
+ for (unsigned int i = 0; i < nodes.size(); i++) {
+ if (!include_deactivated) {
+ push_cfg_path(nodes[i]);
+ bool skip = marked_deactivated(active_cfg);
+ pop_cfg_path();
+ if (skip) {
+ continue;
+ }
+ }
+ cnodes.push_back(nodes[i]);
+ }
+}
+
+/* create all child nodes of current work path that have default values
+ * return true if successful. otherwise return false.
+ * note: assume current work path has just been created so no child
+ * nodes exist.
+ */
+bool
+Cstore::create_default_children()
+{
+ vector<string> tcnodes;
+ get_all_tmpl_child_node_names(tcnodes);
+ bool ret = true;
+ for (unsigned int i = 0; i < tcnodes.size(); i++) {
+ push_tmpl_path(tcnodes[i]);
+ vtw_def def;
+ if (tmpl_node_exists() && tmpl_parse(def)) {
+ if (def.def_default) {
+ // has default value. set it.
+ push_cfg_path(tcnodes[i]);
+ if (!add_node() || !write_value(def.def_default)
+ || !mark_display_default()) {
+ ret = false;
+ }
+ pop_cfg_path();
+ }
+ }
+ pop_tmpl_path();
+ if (!ret) {
+ break;
+ }
+ }
+ return ret;
+}
+
+/* return environment string for "edit"-related operations based on current
+ * work/tmpl paths.
+ */
+void
+Cstore::get_edit_env(string& env)
+{
+ vector<string> lvec;
+ get_edit_level(lvec);
+ string lvl;
+ for (unsigned int i = 0; i < lvec.size(); i++) {
+ if (lvl.length() > 0) {
+ lvl += " ";
+ }
+ lvl += lvec[i];
+ }
+
+ env = ("export " + C_ENV_EDIT_LEVEL + "='" + get_edit_level_path() + "';");
+ env += (" export " + C_ENV_TMPL_LEVEL + "='" + get_tmpl_level_path() + "';");
+ env += (" export " + C_ENV_SHELL_PROMPT + "='"
+ + get_shell_prompt(lvl) + "';");
+}
+
+// return shell prompt string for specified edit level
+string
+Cstore::get_shell_prompt(const string& level)
+{
+ string lvl = level;
+ if (lvl.length() > 0) {
+ lvl = " " + lvl;
+ }
+ return ("[edit" + lvl + "]\\n\\u@\\h# ");
+}
+
+// escape the single quotes in the string for shell
+void
+Cstore::shell_escape_squotes(string& str)
+{
+ size_t sq = 0;
+ while ((sq = str.find('\'', sq)) != str.npos) {
+ str.replace(sq, 1, "'\\''");
+ sq += 4;
+ }
+}
+
diff --git a/src/cstore/cstore.hpp b/src/cstore/cstore.hpp
new file mode 100644
index 0000000..f6a4215
--- /dev/null
+++ b/src/cstore/cstore.hpp
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CSTORE_H_
+#define _CSTORE_H_
+#include <vector>
+#include <string>
+#include <map>
+
+extern "C" {
+#include <cli_val.h>
+#include <cli_val_engine.h>
+}
+
+#define exit_internal(fmt, args...) do \
+ { \
+ output_internal("[%s:%d] " fmt, __FILE__, __LINE__ , ##args); \
+ exit(-1); \
+ } while (0);
+
+/* macros for saving/restoring paths.
+ * note: this allows "nested" save/restore invocations but NOT recursive ones.
+ */
+#define SAVE_PATHS save_paths(&__func__)
+#define RESTORE_PATHS restore_paths(&__func__)
+
+using namespace std;
+
+class Cstore {
+public:
+ Cstore() {};
+ Cstore(string& env);
+ ~Cstore() {};
+
+ // constants
+ static const string C_NODE_STATUS_DELETED;
+ static const string C_NODE_STATUS_ADDED;
+ static const string C_NODE_STATUS_CHANGED;
+ static const string C_NODE_STATUS_STATIC;
+
+ static const string C_ENV_EDIT_LEVEL;
+ static const string C_ENV_TMPL_LEVEL;
+
+ static const string C_ENV_SHELL_PROMPT;
+ static const string C_ENV_SHELL_CWORDS;
+ static const string C_ENV_SHELL_CWORD_COUNT;
+
+ static const string C_ENV_SHAPI_COMP_VALS;
+ static const string C_ENV_SHAPI_LCOMP_VAL;
+ static const string C_ENV_SHAPI_COMP_HELP;
+ static const string C_ENV_SHAPI_HELP_ITEMS;
+ static const string C_ENV_SHAPI_HELP_STRS;
+
+ static const string C_ENUM_SCRIPT_DIR;
+
+ static const size_t MAX_CMD_OUTPUT_SIZE = 4096;
+
+ ////// the public cstore interface
+ //// functions implemented in this base class
+ // these operate on template path
+ bool validateTmplPath(const vector<string>& path_comps, bool validate_vals);
+ bool validateTmplPath(const vector<string>& path_comps, bool validate_vals,
+ vtw_def& def);
+ bool getParsedTmpl(const vector<string>& path_comps,
+ map<string, string>& tmap, bool allow_val = true);
+ void tmplGetChildNodes(const vector<string>& path_comps,
+ vector<string>& cnodes);
+
+ /******
+ * functions for actual CLI operations:
+ * set
+ * delete
+ * activate
+ * deactivate
+ * rename
+ * copy
+ * comment
+ * discard
+ * move (currently this is only available in cfg-cmd-wrapper)
+ * edit-related commands (invoked from shell functions)
+ * completion-related (for completion script)
+ * session-related (setup, teardown, etc.)
+ * load (XXX currently still implemented in perl)
+ *
+ * these operate on the "working config" and the session and MUST NOT
+ * be used by anything other than the listed operations.
+ */
+ // set
+ bool validateSetPath(const vector<string>& path_comps);
+ bool setCfgPath(const vector<string>& path_comps);
+ // delete
+ bool validateDeletePath(const vector<string>& path_comps, vtw_def& def);
+ bool deleteCfgPath(const vector<string>& path_comps, const vtw_def& def);
+ // activate (actually "unmark deactivated" since it is 2-state, not 3)
+ bool validateActivatePath(const vector<string>& path_comps);
+ bool unmarkCfgPathDeactivated(const vector<string>& path_comps);
+ // deactivate
+ bool validateDeactivatePath(const vector<string>& path_comps);
+ bool markCfgPathDeactivated(const vector<string>& path_comps);
+ // rename
+ bool validateRenameArgs(const vector<string>& args);
+ bool renameCfgPath(const vector<string>& args);
+ // copy
+ bool validateCopyArgs(const vector<string>& args);
+ bool copyCfgPath(const vector<string>& args);
+ // comment
+ bool validateCommentArgs(const vector<string>& args, vtw_def& def);
+ bool commentCfgPath(const vector<string>& args, const vtw_def& def);
+ // discard
+ bool discardChanges();
+ // move
+ bool validateMoveArgs(const vector<string>& args);
+ bool moveCfgPath(const vector<string>& args);
+ // edit-related
+ bool getEditEnv(const vector<string>& path_comps, string& env);
+ bool getEditUpEnv(string& env);
+ bool getEditResetEnv(string& env);
+ bool editLevelAtRoot() {
+ return edit_level_at_root();
+ };
+ // completion-related
+ bool getCompletionEnv(const vector<string>& comps, string& env);
+ void getEditLevel(vector<string>& comps) {
+ get_edit_level(comps);
+ };
+ // session-related
+ virtual bool markSessionUnsaved() = 0;
+ virtual bool unmarkSessionUnsaved() = 0;
+ virtual bool sessionUnsaved() = 0;
+ virtual bool sessionChanged() = 0;
+ virtual bool setupSession() = 0;
+ virtual bool teardownSession() = 0;
+ virtual bool inSession() = 0;
+ // common
+ bool markCfgPathChanged(const vector<string>& path_comps);
+ // XXX load
+ //bool unmarkCfgPathDeactivatedDescendants(const vector<string>& path_comps);
+
+ /******
+ * these functions are observers of the current "working config" or
+ * "active config" during a config session.
+ * MOST users of the cstore API should be using these functions (most
+ * likely during a commit operation).
+ *
+ * note that these MUST NOT worry about "deactivated" state.
+ * for these functions, deactivated nodes are equivalent to having been
+ * deleted. in other words, these functions are NOT "deactivate-aware".
+ *
+ * also, the functions that can be used to observe "active config" can
+ * be used outside a config session as well (only when observing active
+ * config, of course).
+ */
+ // observers for "working config" (by default) OR "active config"
+ bool cfgPathExists(const vector<string>& path_comps,
+ bool active_cfg = false);
+ void cfgPathGetChildNodes(const vector<string>& path_comps,
+ vector<string>& cnodes, bool active_cfg = false);
+ bool cfgPathGetValue(const vector<string>& path_comps, string& value,
+ bool active_cfg = false);
+ bool cfgPathGetValues(const vector<string>& path_comps,
+ vector<string>& values, bool active_cfg = false);
+ bool cfgPathGetComment(const vector<string>& path_comps, string& comment,
+ bool active_cfg = false);
+ /* observers for working AND active configs (at the same time).
+ * MUST ONLY be used during config session.
+ */
+ bool cfgPathDeleted(const vector<string>& path_comps);
+ bool cfgPathAdded(const vector<string>& path_comps);
+ bool cfgPathChanged(const vector<string>& path_comps);
+ void cfgPathGetDeletedChildNodes(const vector<string>& path_comps,
+ vector<string>& cnodes);
+ void cfgPathGetChildNodesStatus(const vector<string>& path_comps,
+ map<string, string>& cmap);
+ /* observers for "effective config" (a combination of working config,
+ * active config, and commit processing state) only.
+ * MUST ONLY be used during config session.
+ */
+ bool cfgPathEffective(const vector<string>& path_comps);
+ void cfgPathGetEffectiveChildNodes(const vector<string>& path_comps,
+ vector<string>& cnodes);
+ bool cfgPathGetEffectiveValue(const vector<string>& path_comps,
+ string& value);
+ bool cfgPathGetEffectiveValues(const vector<string>& path_comps,
+ vector<string>& values);
+
+ /******
+ * "deactivate-aware" observers of the current working or active config.
+ * these are the only functions that are allowed to see the "deactivate"
+ * state of config nodes.
+ *
+ * these functions MUST ONLY be used by operations that NEED to distinguish
+ * between deactivated nodes and deleted nodes. below is the list
+ * of operations that are allowed to use these functions:
+ * * configuration output (show, save, load)
+ *
+ * operations that are not on the above list MUST NOT use these
+ * "deactivate-aware" functions.
+ *
+ * note: the last argument "include_deactivated" for the DA functions
+ * is for implementation convenience and does not need to be
+ * passed in when calling them.
+ */
+ // working or active config
+ bool cfgPathDeactivated(const vector<string>& path_comps,
+ bool active_cfg = false);
+ bool cfgPathMarkedDeactivated(const vector<string>& path_comps,
+ bool active_cfg = false);
+ bool cfgPathExistsDA(const vector<string>& path_comps,
+ bool active_cfg = false,
+ bool include_deactivated = true);
+ void cfgPathGetChildNodesDA(const vector<string>& path_comps,
+ vector<string>& cnodes,
+ bool active_cfg = false,
+ bool include_deactivated = true);
+ bool cfgPathGetValueDA(const vector<string>& path_comps, string& value,
+ bool active_cfg = false,
+ bool include_deactivated = true);
+ bool cfgPathGetValuesDA(const vector<string>& path_comps,
+ vector<string>& values,
+ bool active_cfg = false,
+ bool include_deactivated = true);
+ // working AND active configs
+ void cfgPathGetDeletedChildNodesDA(const vector<string>& path_comps,
+ vector<string>& cnodes,
+ bool include_deactivated = true);
+ void cfgPathGetChildNodesStatusDA(const vector<string>& path_comps,
+ map<string, string>& cmap);
+
+
+ /* these are internal API functions and operate on current cfg and
+ * tmpl paths during cstore operations. they are only used to work around
+ * the limitations of the original CLI library implementation and MUST NOT
+ * be used by anyone other than the original CLI library.
+ */
+ bool getVarRef(const string& ref_str, clind_val& cval, bool from_active);
+ bool setVarRef(const string& ref_str, const string& value, bool to_active);
+
+protected:
+ ////// functions for subclasses
+ void output_user(const char *fmt, ...);
+ void output_internal(const char *fmt, ...);
+
+private:
+ ////// member class
+ // for variable reference
+ class VarRef;
+
+ ////// virtual
+ /* "path modifiers"
+ * note: only these functions are allowed to permanently change the paths.
+ * all other functions must "preserve" the paths (but can change paths
+ * "during" an invocation), i.e., the paths after invocation must be
+ * the same as before invocation.
+ */
+ // begin path modifiers
+ virtual void push_tmpl_path(const string& path_comp) = 0;
+ virtual void push_tmpl_path_tag() = 0;
+ virtual string pop_tmpl_path() = 0;
+ virtual void push_cfg_path(const string& path_comp) = 0;
+ virtual void push_cfg_path_val() = 0;
+ virtual string pop_cfg_path() = 0;
+ virtual void append_cfg_path(const vector<string>& path_comps) = 0;
+ virtual void reset_paths() = 0;
+ virtual void save_paths(const void *handle = NULL) = 0;
+ virtual void restore_paths(const void *handle = NULL) = 0;
+ virtual bool cfg_path_at_root() = 0;
+ virtual bool tmpl_path_at_root() = 0;
+ // end path modifiers
+
+ // these operate on current tmpl path
+ virtual bool tmpl_node_exists() = 0;
+ virtual bool tmpl_parse(vtw_def& def) = 0;
+
+ // these operate on current work path (or active with "active_cfg")
+ virtual bool remove_node() = 0;
+ virtual void get_all_child_node_names_impl(vector<string>& cnodes,
+ bool active_cfg = false) = 0;
+ virtual void get_all_tmpl_child_node_names(vector<string>& cnodes) = 0;
+ virtual bool write_value_vec(const vector<string>& vvec,
+ bool active_cfg = false) = 0;
+ virtual bool add_node() = 0;
+ virtual bool rename_child_node(const string& oname, const string& nname) = 0;
+ virtual bool copy_child_node(const string& oname, const string& nname) = 0;
+ virtual bool mark_display_default() = 0;
+ virtual bool unmark_display_default() = 0;
+ virtual bool mark_deactivated() = 0;
+ virtual bool unmark_deactivated() = 0;
+ virtual bool unmark_deactivated_descendants() = 0;
+ virtual bool mark_changed() = 0;
+ virtual bool remove_comment() = 0;
+ virtual bool set_comment(const string& comment) = 0;
+ virtual bool discard_changes(unsigned long long& num_removed) = 0;
+
+ // observers for current work path
+ virtual bool marked_changed() = 0;
+ virtual bool marked_display_default() = 0;
+
+ // observers for current work path or active path
+ virtual bool read_value_vec(vector<string>& vvec, bool active_cfg) = 0;
+ virtual bool cfg_node_exists(bool active_cfg) = 0;
+ virtual bool marked_deactivated(bool active_cfg) = 0;
+ virtual bool get_comment(string& comment, bool active_cfg) = 0;
+
+ // observers during commit operation
+ virtual bool marked_committed(const vtw_def& def, bool is_set) = 0;
+
+ // these operate on both current tmpl and work paths
+ virtual bool validate_val_impl(vtw_def *def, char *value) = 0;
+
+ // observers for "edit/tmpl levels" (for "edit"-related operations)
+ /* note that these should be handled in the base class since they
+ * should not be implementation-specific. however, current definitions
+ * of these "levels" environment vars require filesystem-specific
+ * "escapes", so handle them in derived class.
+ *
+ * revisit when the env vars are redefined.
+ *
+ * these operate on current "work" or "tmpl" path, i.e., cfg/tmpl path
+ * needs to be set up before calling these.
+ */
+ virtual string get_edit_level_path() = 0;
+ virtual string get_tmpl_level_path() = 0;
+ virtual void get_edit_level(vector<string>& path_comps) = 0;
+ virtual bool edit_level_at_root() = 0;
+
+ // these are for testing/debugging
+ virtual string cfg_path_to_str() = 0;
+ virtual string tmpl_path_to_str() = 0;
+
+ ////// implemented
+ // begin path modifiers (only these can change path permanently)
+ bool append_tmpl_path(const vector<string>& path_comps, bool& is_tag);
+ bool append_tmpl_path(const vector<string>& path_comps) {
+ bool dummy;
+ return append_tmpl_path(path_comps, dummy);
+ };
+ bool append_tmpl_path(const string& path_comp, bool& is_tag) {
+ return append_tmpl_path(vector<string>(1, path_comp), is_tag);
+ };
+ bool append_tmpl_path(const string& path_comp) {
+ bool dummy;
+ return append_tmpl_path(path_comp, dummy);
+ };
+ // end path modifiers
+
+ // these require full path
+ // (note: get_parsed_tmpl also uses current tmpl path)
+ bool get_parsed_tmpl(const vector<string>& path_comps, bool validate_vals,
+ vtw_def& def, string& error);
+ bool get_parsed_tmpl(const vector<string>& path_comps, bool validate_vals,
+ vtw_def& def) {
+ string dummy;
+ return get_parsed_tmpl(path_comps, validate_vals, def, dummy);
+ };
+ bool validate_act_deact(const vector<string>& path_comps, const string& op,
+ vtw_def& def);
+ bool validate_rename_copy(const vector<string>& args, const string& op);
+ bool conv_move_args_for_rename(const vector<string>& args,
+ vector<string>& edit_path_comps,
+ vector<string>& rn_args);
+ bool cfg_path_exists(const vector<string>& path_comps,
+ bool active_cfg, bool include_deactivated);
+
+ // these operate on current work path (or active with "active_cfg")
+ bool remove_tag();
+ bool remove_value_from_multi(const string& value);
+ bool write_value(const string& value, bool active_cfg = false) {
+ vector<string> vvec(1, value);
+ return write_value_vec(vvec, active_cfg);
+ };
+ bool add_tag(const vtw_def& def);
+ bool add_value_to_multi(const vtw_def& def, const string& value);
+ bool add_child_node(const string& name) {
+ push_cfg_path(name);
+ bool ret = add_node();
+ pop_cfg_path();
+ return ret;
+ };
+ void get_all_child_node_names(vector<string>& cnodes, bool active_cfg,
+ bool include_deactivated);
+
+ // observers for work path or active path
+ bool cfg_value_exists(const string& value, bool active_cfg);
+
+ // these operate on both current tmpl and work paths
+ bool validate_val(const vtw_def *def, const string& value);
+ bool create_default_children();
+ void get_edit_env(string& env);
+
+ // util functions
+ string get_shell_prompt(const string& level);
+ void shell_escape_squotes(string& str);
+};
+
+#endif /* _CSTORE_H_ */
+
diff --git a/src/cstore/unionfs/cstore-unionfs.cpp b/src/cstore/unionfs/cstore-unionfs.cpp
new file mode 100644
index 0000000..977a597
--- /dev/null
+++ b/src/cstore/unionfs/cstore-unionfs.cpp
@@ -0,0 +1,1078 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <map>
+#include <fstream>
+#include <sstream>
+
+#include <errno.h>
+#include <sys/mount.h>
+
+extern "C" {
+#include "cli_val.h"
+#include "cli_objects.h"
+}
+
+#include "cstore-unionfs.hpp"
+
+
+////// constants
+// environment vars defining root dirs
+const string UnionfsCstore::C_ENV_TMPL_ROOT = "VYATTA_CONFIG_TEMPLATE";
+const string UnionfsCstore::C_ENV_WORK_ROOT = "VYATTA_TEMP_CONFIG_DIR";
+const string UnionfsCstore::C_ENV_ACTIVE_ROOT
+ = "VYATTA_ACTIVE_CONFIGURATION_DIR";
+const string UnionfsCstore::C_ENV_CHANGE_ROOT = "VYATTA_CHANGES_ONLY_DIR";
+const string UnionfsCstore::C_ENV_TMP_ROOT = "VYATTA_CONFIG_TMP";
+
+// default root dirs/paths
+const string UnionfsCstore::C_DEF_TMPL_ROOT
+ = "/opt/vyatta/share/vyatta-cfg/templates";
+const string UnionfsCstore::C_DEF_CFG_ROOT
+ = "/opt/vyatta/config";
+const string UnionfsCstore::C_DEF_ACTIVE_ROOT
+ = UnionfsCstore::C_DEF_CFG_ROOT + "/active";
+const string UnionfsCstore::C_DEF_CHANGE_PREFIX = "/tmp/changes_only_";
+const string UnionfsCstore::C_DEF_WORK_PREFIX
+ = UnionfsCstore::C_DEF_CFG_ROOT + "/tmp/new_config_";
+const string UnionfsCstore::C_DEF_TMP_PREFIX
+ = UnionfsCstore::C_DEF_CFG_ROOT + "/tmp/tmp_";
+
+// markers
+const string UnionfsCstore::C_MARKER_DEF_VALUE = "def";
+const string UnionfsCstore::C_MARKER_DEACTIVATE = ".disable";
+const string UnionfsCstore::C_MARKER_CHANGED = ".modified";
+const string UnionfsCstore::C_MARKER_UNSAVED = ".unsaved";
+const string UnionfsCstore::C_COMMITTED_MARKER_FILE = "/tmp/.changes";
+const string UnionfsCstore::C_COMMENT_FILE = ".comment";
+
+
+////// static
+static map<char, string> _fs_escape_chars;
+static map<string, char> _fs_unescape_chars;
+static void
+_init_fs_escape_chars()
+{
+ _fs_escape_chars[-1] = "\%\%\%";
+ _fs_escape_chars['%'] = "\%25";
+ _fs_escape_chars['/'] = "\%2F";
+
+ _fs_unescape_chars["\%\%\%"] = -1;
+ _fs_unescape_chars["\%25"] = '%';
+ _fs_unescape_chars["\%2F"] = '/';
+}
+
+static string
+_escape_char(char c)
+{
+ map<char, string>::iterator p = _fs_escape_chars.find(c);
+ if (p != _fs_escape_chars.end()) {
+ return _fs_escape_chars[c];
+ } else {
+ return string(1, c);
+ }
+}
+
+static map<string, string> _escape_path_name_cache;
+
+static string
+_escape_path_name(const string& path)
+{
+ map<string, string>::iterator p = _escape_path_name_cache.find(path);
+ if (p != _escape_path_name_cache.end()) {
+ // found escaped string in cache. just return it.
+ return _escape_path_name_cache[path];
+ }
+
+ // special case for empty string
+ string npath = (path.size() == 0) ? _fs_escape_chars[-1] : "";
+ for (unsigned int i = 0; i < path.size(); i++) {
+ npath += _escape_char(path[i]);
+ }
+
+ // cache it before return
+ _escape_path_name_cache[path] = npath;
+ return npath;
+}
+
+static map<string, string> _unescape_path_name_cache;
+
+static string
+_unescape_path_name(const string& path)
+{
+ map<string, string>::iterator p = _unescape_path_name_cache.find(path);
+ if (p != _unescape_path_name_cache.end()) {
+ // found unescaped string in cache. just return it.
+ return _unescape_path_name_cache[path];
+ }
+
+ // assume all escape patterns are 3-char
+ string npath = "";
+ for (unsigned int i = 0; i < path.size(); i++) {
+ if ((path.size() - i) < 3) {
+ npath += path.substr(i);
+ break;
+ }
+ string s = path.substr(i, 3);
+ map<string, char>::iterator p = _fs_unescape_chars.find(s);
+ if (p != _fs_unescape_chars.end()) {
+ char c = _fs_unescape_chars[s];
+ if (path.size() == 3 && c == -1) {
+ // special case for empty string
+ npath = "";
+ break;
+ }
+ npath += string(1, _fs_unescape_chars[s]);
+ // skip the escape sequence
+ i += 2;
+ } else {
+ npath += path.substr(i, 1);
+ }
+ }
+ // cache it before return
+ _unescape_path_name_cache[path] = npath;
+ return npath;
+}
+
+
+////// constructor/destructor
+/* "current session" constructor.
+ * this constructor sets up the object from environment.
+ * used when environment is already set up, i.e., when operating on the
+ * "current" config session. e.g., in the following scenarios
+ * configure commands
+ * perl module
+ * shell "current session" api
+ *
+ * note: this also applies when using the cstore in operational mode,
+ * in which case only the template root and the active root will be
+ * valid.
+ */
+UnionfsCstore::UnionfsCstore(bool use_edit_level)
+{
+ // set up root dir strings
+ char *val;
+ if ((val = getenv(C_ENV_TMPL_ROOT.c_str()))) {
+ tmpl_path = val;
+ } else {
+ tmpl_path = C_DEF_TMPL_ROOT;
+ }
+ tmpl_root = tmpl_path; // save a copy of tmpl root
+ if ((val = getenv(C_ENV_WORK_ROOT.c_str()))) {
+ work_root = val;
+ }
+ if ((val = getenv(C_ENV_TMP_ROOT.c_str()))) {
+ tmp_root = val;
+ }
+ if ((val = getenv(C_ENV_ACTIVE_ROOT.c_str()))) {
+ active_root = val;
+ } else {
+ active_root = C_DEF_ACTIVE_ROOT;
+ }
+ if ((val = getenv(C_ENV_CHANGE_ROOT.c_str()))) {
+ change_root = val;
+ }
+
+ /* note: the original perl API module does not use the edit levels
+ * from environment. only the actual CLI operations use them.
+ * so here make it an option.
+ */
+ if (use_edit_level) {
+ // set up path strings
+ if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) {
+ mutable_cfg_path = val;
+ }
+ if ((val = getenv(C_ENV_TMPL_LEVEL.c_str()))) {
+ tmpl_path /= val;
+ }
+ }
+ _init_fs_escape_chars();
+}
+
+/* "specific session" constructor.
+ * this constructor sets up the object for the specified session ID and
+ * returns an environment string that can be "evaled" to set up the
+ * shell environment.
+ *
+ * used when the session environment needs to be established. this is
+ * mainly for the shell functions that set up configuration sessions.
+ * i.e., the "vyatta-cfg-cmd-wrapper" (on boot or for GUI etc.) and
+ * the cfg completion script (when entering configure mode).
+ *
+ * sid: session ID.
+ * env: (output) environment string.
+ *
+ * note: this does NOT set up the session. caller needs to use the
+ * explicit session setup/teardown functions as needed.
+ */
+UnionfsCstore::UnionfsCstore(const string& sid, string& env)
+ : Cstore(env)
+{
+ tmpl_root = C_DEF_TMPL_ROOT;
+ tmpl_path = tmpl_root;
+ active_root = C_DEF_ACTIVE_ROOT;
+ work_root = (C_DEF_WORK_PREFIX + sid);
+ change_root = (C_DEF_CHANGE_PREFIX + sid);
+ tmp_root = (C_DEF_TMP_PREFIX + sid);
+
+ string declr = " declare -x -r "; // readonly vars
+ env += " umask 002; {";
+ env += (declr + C_ENV_ACTIVE_ROOT + "=" + active_root.file_string());
+ env += (declr + C_ENV_CHANGE_ROOT + "=" + change_root.file_string() + ";");
+ env += (declr + C_ENV_WORK_ROOT + "=" + work_root.file_string() + ";");
+ env += (declr + C_ENV_TMP_ROOT + "=" + tmp_root.file_string() + ";");
+ env += (declr + C_ENV_TMPL_ROOT + "=" + tmpl_root.file_string() + ";");
+ env += " } >&/dev/null || true";
+
+ // set up path strings using level vars
+ char *val;
+ if ((val = getenv(C_ENV_EDIT_LEVEL.c_str()))) {
+ mutable_cfg_path = val;
+ }
+ if ((val = getenv(C_ENV_TMPL_LEVEL.c_str()))) {
+ tmpl_path /= val;
+ }
+
+ _init_fs_escape_chars();
+}
+
+UnionfsCstore::~UnionfsCstore()
+{
+}
+
+
+////// public virtual functions declared in base class
+bool
+UnionfsCstore::markSessionUnsaved()
+{
+ b_fs::path marker = work_root / C_MARKER_UNSAVED;
+ if (b_fs::exists(marker)) {
+ // already marked. treat as success.
+ return true;
+ }
+ if (!create_file(marker.file_string())) {
+ output_internal("failed to mark unsaved [%s]\n",
+ marker.file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::unmarkSessionUnsaved()
+{
+ b_fs::path marker = work_root / C_MARKER_UNSAVED;
+ if (!b_fs::exists(marker)) {
+ // not marked. treat as success.
+ return true;
+ }
+ try {
+ b_fs::remove(marker);
+ } catch (...) {
+ output_internal("failed to unmark unsaved [%s]\n",
+ marker.file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::sessionUnsaved()
+{
+ b_fs::path marker = work_root / C_MARKER_UNSAVED;
+ return b_fs::exists(marker);
+}
+
+bool
+UnionfsCstore::sessionChanged()
+{
+ b_fs::path marker = work_root / C_MARKER_CHANGED;
+ return b_fs::exists(marker);
+}
+
+/* set up the session associated with this object.
+ * the session comes from either the environment or the session ID
+ * (see the two different constructors).
+ */
+bool
+UnionfsCstore::setupSession()
+{
+ if (!b_fs::exists(work_root)) {
+ // session doesn't exist. create dirs.
+ try {
+ b_fs::create_directories(work_root);
+ b_fs::create_directories(change_root);
+ b_fs::create_directories(tmp_root);
+ if (!b_fs::exists(active_root)) {
+ // this should only be needed on boot
+ b_fs::create_directories(active_root);
+ }
+ } catch (...) {
+ output_internal("setup session failed to create session directories\n");
+ return false;
+ }
+
+ // union mount
+ string mopts = ("dirs=" + change_root.file_string() + "=rw:"
+ + active_root.file_string() + "=ro");
+ if (mount("unionfs", work_root.file_string().c_str(), "unionfs", 0,
+ mopts.c_str()) != 0) {
+ output_internal("setup session mount failed [%s][%s]\n",
+ strerror(errno), work_root.file_string().c_str());
+ return false;
+ }
+ } else if (!b_fs::is_directory(work_root)) {
+ output_internal("setup session not dir [%s]\n",
+ work_root.file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+/* tear down the session associated with this object.
+ * the session comes from either the environment or the session ID
+ * (see the two different constructors).
+ */
+bool
+UnionfsCstore::teardownSession()
+{
+ // check if session exists
+ string wstr = work_root.file_string();
+ if (wstr.empty() || wstr.find(C_DEF_WORK_PREFIX) != 0
+ || !b_fs::exists(work_root) || !b_fs::is_directory(work_root)) {
+ // no session
+ output_internal("teardown invalid session [%s]\n", wstr.c_str());
+ return false;
+ }
+
+ // unmount the work root (union)
+ if (umount(wstr.c_str()) != 0) {
+ output_internal("teardown session umount failed [%s][%s]\n",
+ strerror(errno), wstr.c_str());
+ return false;
+ }
+
+ // remove session directories
+ bool ret = false;
+ try {
+ if (b_fs::remove_all(work_root) != 0
+ && b_fs::remove_all(change_root) != 0
+ && b_fs::remove_all(tmp_root) != 0) {
+ ret = true;
+ }
+ } catch (...) {
+ }
+ if (!ret) {
+ output_internal("failed to remove session directories\n");
+ }
+ return ret;
+}
+
+/* whether an actual config session is associated with this object.
+ * the session comes from either the environment or the session ID
+ * (see the two different constructors).
+ */
+bool
+UnionfsCstore::inSession()
+{
+ string wstr = work_root.file_string();
+ return (!wstr.empty() && wstr.find(C_DEF_WORK_PREFIX) == 0
+ && b_fs::exists(work_root) && b_fs::is_directory(work_root));
+}
+
+
+////// virtual functions defined in base class
+/* check if current tmpl_path is a valid tmpl dir.
+ * return true if valid. otherwise return false.
+ */
+bool
+UnionfsCstore::tmpl_node_exists()
+{
+ return (b_fs::exists(tmpl_path) && b_fs::is_directory(tmpl_path));
+}
+
+/* parse template at current tmpl_path.
+ * def: for storing parsed template.
+ * return true if successful. otherwise return false.
+ */
+bool
+UnionfsCstore::tmpl_parse(vtw_def& def)
+{
+ push_tmpl_path(DEF_NAME);
+ bool ret = (b_fs::exists(tmpl_path) && b_fs::is_regular(tmpl_path)
+ && parse_def(&def, tmpl_path.file_string().c_str(), FALSE) == 0);
+ pop_tmpl_path();
+ return ret;
+}
+
+bool
+UnionfsCstore::cfg_node_exists(bool active_cfg)
+{
+ b_fs::path p = (active_cfg ? get_active_path() : get_work_path());
+ return (b_fs::exists(p) && b_fs::is_directory(p));
+}
+
+bool
+UnionfsCstore::add_node()
+{
+ bool ret = true;
+ try {
+ if (!b_fs::create_directory(get_work_path())) {
+ // already exists. shouldn't call this function.
+ ret = false;
+ }
+ } catch (...) {
+ ret = false;
+ }
+ if (!ret) {
+ output_internal("failed to add node [%s]\n",
+ get_work_path().file_string().c_str());
+ }
+ return ret;
+}
+
+bool
+UnionfsCstore::remove_node()
+{
+ if (!b_fs::exists(get_work_path()) || !b_fs::is_directory(get_work_path())) {
+ output_internal("remove non-existent node [%s]\n",
+ get_work_path().file_string().c_str());
+ return false;
+ }
+ bool ret = false;
+ try {
+ if (b_fs::remove_all(get_work_path()) != 0) {
+ ret = true;
+ }
+ } catch (...) {
+ ret = false;
+ }
+ if (!ret) {
+ output_internal("failed to remove node [%s]\n",
+ get_work_path().file_string().c_str());
+ }
+ return ret;
+}
+
+void
+UnionfsCstore::get_all_child_node_names_impl(vector<string>& cnodes,
+ bool active_cfg)
+{
+ b_fs::path p = (active_cfg ? get_active_path() : get_work_path());
+ get_all_child_dir_names(p, cnodes);
+
+ /* XXX special cases to emulate original perl API behavior.
+ * original perl listNodes() and listOrigNodes() return everything
+ * under a node (except for ".*"), including "node.val" and "def".
+ *
+ * perl API should operate at abstract level and should not access
+ * such implementation-specific details. however, currently
+ * things like config output depend on this behavior, so this
+ * function needs to return them for now.
+ *
+ * use a whilelist-approach, i.e., only add the following:
+ * node.val
+ * def
+ */
+ if (b_fs::exists(p / VAL_NAME) && b_fs::is_regular(p / VAL_NAME)) {
+ cnodes.push_back(VAL_NAME);
+ }
+ if (b_fs::exists(p / C_MARKER_DEF_VALUE)
+ && b_fs::is_regular(p / C_MARKER_DEF_VALUE)) {
+ cnodes.push_back(C_MARKER_DEF_VALUE);
+ }
+}
+
+bool
+UnionfsCstore::read_value_vec(vector<string>& vvec, bool active_cfg)
+{
+ push_cfg_path_val();
+ b_fs::path vpath = (active_cfg ? get_active_path() : get_work_path());
+ bool ret = false;
+ do {
+ string ostr;
+ if (!read_whole_file(vpath, ostr)) {
+ break;
+ }
+
+ /* XXX original implementation used to remove a trailing '\n' after
+ * a read. it was only necessary because it was adding a '\n' when
+ * writing the file. don't remove anything now since we shouldn't
+ * be writing it any more.
+ */
+ // separate values using newline as delimiter
+ unsigned int start_idx = 0, idx = 0;
+ for (; idx < ostr.size(); idx++) {
+ if (ostr[idx] == '\n') {
+ // got a value
+ vvec.push_back(ostr.substr(start_idx, (idx - start_idx)));
+ start_idx = idx + 1;
+ }
+ }
+ if (start_idx < ostr.size()) {
+ vvec.push_back(ostr.substr(start_idx, (idx - start_idx)));
+ } else {
+ // last char is a newline => another empty value
+ vvec.push_back("");
+ }
+ ret = true;
+ } while (0);
+ pop_cfg_path();
+ return ret;
+}
+
+bool
+UnionfsCstore::write_value_vec(const vector<string>& vvec, bool active_cfg)
+{
+ push_cfg_path_val();
+ bool ret = false;
+ b_fs::path wp = (active_cfg ? get_active_path() : get_work_path());
+ do {
+ if (b_fs::exists(wp) && !b_fs::is_regular(wp)) {
+ // not a file
+ break;
+ }
+
+ string ostr = "";
+ for (unsigned int i = 0; i < vvec.size(); i++) {
+ if (i > 0) {
+ // subsequent values require delimiter
+ ostr += "\n";
+ }
+ ostr += vvec[i];
+ }
+
+ if (!write_file(wp.file_string().c_str(), ostr)) {
+ break;
+ }
+ ret = true;
+ } while (0);
+ pop_cfg_path();
+ if (!ret) {
+ output_internal("failed to write node value [%s]\n",
+ wp.file_string().c_str());
+ }
+ return ret;
+}
+
+bool
+UnionfsCstore::rename_child_node(const string& oname, const string& nname)
+{
+ b_fs::path opath = get_work_path() / oname;
+ b_fs::path npath = get_work_path() / nname;
+ if (!b_fs::exists(opath) || !b_fs::is_directory(opath)
+ || b_fs::exists(npath)) {
+ output_internal("cannot rename node [%s,%s,%s]\n",
+ get_work_path().file_string().c_str(),
+ oname.c_str(), nname.c_str());
+ return false;
+ }
+ bool ret = true;
+ try {
+ /* somehow b_fs::rename() can't be used here as it considers the operation
+ * "Invalid cross-device link" and fails with an exception, probably due
+ * to unionfs in some way.
+ * do it the hard way.
+ */
+ recursive_copy_dir(opath, npath);
+ if (b_fs::remove_all(opath) == 0) {
+ ret = false;
+ }
+ } catch (...) {
+ ret = false;
+ }
+ if (!ret) {
+ output_internal("failed to rename node [%s,%s]\n",
+ opath.file_string().c_str(),
+ npath.file_string().c_str());
+ }
+ return ret;
+}
+
+bool
+UnionfsCstore::copy_child_node(const string& oname, const string& nname)
+{
+ b_fs::path opath = get_work_path() / oname;
+ b_fs::path npath = get_work_path() / nname;
+ if (!b_fs::exists(opath) || !b_fs::is_directory(opath)
+ || b_fs::exists(npath)) {
+ output_internal("cannot copy node [%s,%s,%s]\n",
+ get_work_path().file_string().c_str(),
+ oname.c_str(), nname.c_str());
+ return false;
+ }
+ try {
+ recursive_copy_dir(opath, npath);
+ } catch (...) {
+ output_internal("failed to copy node [%s,%s,%s]\n",
+ get_work_path().file_string().c_str(),
+ oname.c_str(), nname.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::mark_display_default()
+{
+ b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE;
+ if (b_fs::exists(marker)) {
+ // already marked. treat as success.
+ return true;
+ }
+ if (!create_file(marker.file_string())) {
+ output_internal("failed to mark default [%s]\n",
+ get_work_path().file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::unmark_display_default()
+{
+ b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE;
+ if (!b_fs::exists(marker)) {
+ // not marked. treat as success.
+ return true;
+ }
+ try {
+ b_fs::remove(marker);
+ } catch (...) {
+ output_internal("failed to unmark default [%s]\n",
+ get_work_path().file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::marked_display_default()
+{
+ b_fs::path marker = get_work_path() / C_MARKER_DEF_VALUE;
+ return b_fs::exists(marker);
+}
+
+bool
+UnionfsCstore::marked_deactivated(bool active_cfg)
+{
+ b_fs::path p = (active_cfg ? get_active_path() : get_work_path());
+ b_fs::path marker = p / C_MARKER_DEACTIVATE;
+ return b_fs::exists(marker);
+}
+
+bool
+UnionfsCstore::mark_deactivated()
+{
+ b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE;
+ if (b_fs::exists(marker)) {
+ // already marked. treat as success.
+ return true;
+ }
+ if (!create_file(marker.file_string())) {
+ output_internal("failed to mark deactivated [%s]\n",
+ get_work_path().file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::unmark_deactivated()
+{
+ b_fs::path marker = get_work_path() / C_MARKER_DEACTIVATE;
+ if (!b_fs::exists(marker)) {
+ // not deactivated. treat as success.
+ return true;
+ }
+ try {
+ b_fs::remove(marker);
+ } catch (...) {
+ output_internal("failed to unmark deactivated [%s]\n",
+ get_work_path().file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::unmark_deactivated_descendants()
+{
+ bool ret = false;
+ do {
+ // sanity check
+ if (!b_fs::is_directory(get_work_path())) {
+ break;
+ }
+
+ vector<b_fs::path> markers;
+ b_fs::recursive_directory_iterator di(get_work_path());
+ for (; di != b_fs::recursive_directory_iterator(); ++di) {
+ if (!b_fs::is_regular(di->path())
+ || di->path().filename() != C_MARKER_DEACTIVATE) {
+ // not marker
+ continue;
+ }
+ if (di->path().parent_path() == get_work_path()) {
+ // don't unmark the node itself
+ continue;
+ }
+ markers.push_back(di->path());
+ }
+ try {
+ for (unsigned int i = 0; i < markers.size(); i++) {
+ b_fs::remove(markers[i]);
+ }
+ } catch (...) {
+ break;
+ }
+ ret = true;
+ } while (0);
+ if (!ret) {
+ output_internal("failed to unmark deactivated descendants [%s]\n",
+ get_work_path().file_string().c_str());
+ }
+ return ret;
+}
+
+bool
+UnionfsCstore::mark_changed()
+{
+ if (!mutable_cfg_path.has_parent_path()) {
+ /* at root, mark changed. root marker is needed by the original
+ * implementation as an indication of whether the whole config
+ * has changed.
+ */
+ b_fs::path marker = get_work_path() / C_MARKER_CHANGED;
+ if (b_fs::exists(marker)) {
+ // already marked. treat as success.
+ return true;
+ }
+ if (!create_file(marker.file_string())) {
+ output_internal("failed to mark changed [%s]\n",
+ get_work_path().file_string().c_str());
+ return false;
+ }
+ return true;
+ }
+
+ /* XXX not at root => nop for now.
+ * we should be marking changed here. however, as commit is still
+ * using its own unionfs implementation, it will not understand the
+ * markers and therefore will not perform the necessary cleanup when
+ * it's done.
+ *
+ * for now, don't mark anything besides root. the query function
+ * will use unionfs-specific implementation (changes-only dir).
+ */
+ return true;
+}
+
+// remove the comment at the current work path
+bool
+UnionfsCstore::remove_comment()
+{
+ b_fs::path cfile = get_work_path() / C_COMMENT_FILE;
+ if (!b_fs::exists(cfile)) {
+ return false;
+ }
+ try {
+ b_fs::remove(cfile);
+ } catch (...) {
+ output_internal("failed to remove comment [%s]\n",
+ cfile.file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+// set comment at the current work path
+bool
+UnionfsCstore::set_comment(const string& comment)
+{
+ b_fs::path cfile = get_work_path() / C_COMMENT_FILE;
+ return write_file(cfile.file_string(), comment);
+}
+
+// discard all changes in working config
+bool
+UnionfsCstore::discard_changes(unsigned long long& num_removed)
+{
+ // unionfs-specific implementation
+ vector<b_fs::path> files;
+ vector<b_fs::path> directories;
+ // iterate through all entries in change root
+ b_fs::directory_iterator di(change_root);
+ for (; di != b_fs::directory_iterator(); ++di) {
+ if (b_fs::is_directory(di->path())) {
+ directories.push_back(di->path());
+ } else {
+ files.push_back(di->path());
+ }
+ }
+
+ // remove and count
+ try {
+ num_removed = 0;
+ for (unsigned int i = 0; i < files.size(); i++) {
+ b_fs::remove(files[i]);
+ num_removed++;
+ }
+ for (unsigned int i = 0; i < directories.size(); i++) {
+ num_removed += b_fs::remove_all(directories[i]);
+ }
+ } catch (...) {
+ output_internal("discard failed [%s]\n",
+ change_root.file_string().c_str());
+ return false;
+ }
+ return true;
+}
+
+// get comment at the current work or active path
+bool
+UnionfsCstore::get_comment(string& comment, bool active_cfg)
+{
+ b_fs::path cfile = (active_cfg ? get_active_path() : get_work_path());
+ cfile /= C_COMMENT_FILE;
+ return read_whole_file(cfile, comment);
+}
+
+bool
+UnionfsCstore::marked_changed()
+{
+ /* this function is only called by cfgPathChanged() in base class.
+ *
+ * XXX currently just use the changes_only dir for this query.
+ * see explanation in mark_changed().
+ *
+ * this implementation relies on the fact that cfgPathChanged()
+ * includes deleted/added nodes (including deactivated/activated
+ * nodes since it's NOT deactivate-aware). if that is not the case,
+ * result will be different between deleted nodes (NOT IN
+ * changes_only) and deactivated nodes (IN changes_only).
+ */
+ return b_fs::exists(get_change_path());
+}
+
+/* XXX currently "committed marking" is done inside commit.
+ * TODO move "committed marking" out of commit and into low-level
+ * implementation (here).
+ */
+/* return whether current "cfg path" has been committed, i.e., whether
+ * the set or delete operation on the path has been processed by commit.
+ * is_set: whether the operation is set (for sanity check as there can
+ * be only one operation on the path).
+ */
+bool
+UnionfsCstore::marked_committed(const vtw_def& def, bool is_set)
+{
+ b_fs::path cpath = mutable_cfg_path;
+ string com_str = cpath.file_string() + "/";
+ if (def.is_value && !def.tag) {
+ // path includes leaf value. construct the right string.
+ string val = _unescape_path_name(cpath.filename());
+ cpath = cpath.parent_path();
+ /* XXX current commit implementation escapes value strings for
+ * single-value nodes but not for multi-value nodes for some
+ * reason. the following match current behavior.
+ */
+ if (!def.multi) {
+ val = _escape_path_name(val);
+ }
+ com_str = cpath.file_string() + "/value:" + val;
+ }
+ com_str = (is_set ? "+ " : "- ") + com_str;
+ return committed_marker_exists(com_str);
+}
+
+bool
+UnionfsCstore::validate_val_impl(vtw_def *def, char *value)
+{
+ /* XXX filesystem paths/accesses are completely embedded in var ref lib.
+ * for now, treat the lib as a unionfs-specific implementation.
+ * generalizing it will need a rewrite.
+ * set the handle to be used during validate_value() for var ref
+ * processing. this is a global var in cli_new.c.
+ */
+ var_ref_handle = (void *) this;
+ bool ret = validate_value(def, value);
+ var_ref_handle = NULL;
+ return ret;
+}
+
+void
+UnionfsCstore::get_edit_level(vector<string>& pcomps) {
+ b_fs::path opath = mutable_cfg_path; // use a copy
+ while (opath.has_parent_path()) {
+ pcomps.insert(pcomps.begin(), pop_path(opath));
+ }
+}
+
+string
+UnionfsCstore::cfg_path_to_str() {
+ string cpath = mutable_cfg_path.file_string();
+ if (cpath.length() == 0) {
+ cpath = "/";
+ }
+ return cpath;
+}
+
+string
+UnionfsCstore::tmpl_path_to_str() {
+ // return only the mutable part
+ string tpath = tmpl_path.file_string();
+ tpath.erase(0, tmpl_root.file_string().length());
+ if (tpath.length() == 0) {
+ tpath = "/";
+ }
+ return tpath;
+}
+
+
+////// private functions
+void
+UnionfsCstore::push_path(b_fs::path& old_path, const string& new_comp)
+{
+ string comp = _escape_path_name(new_comp);
+ old_path /= comp;
+}
+
+string
+UnionfsCstore::pop_path(b_fs::path& path)
+{
+ string ret = _unescape_path_name(path.filename());
+ /* note: contrary to documentation, remove_filename() does not remove
+ * trailing slash.
+ */
+ path = path.parent_path();
+ return ret;
+}
+
+void
+UnionfsCstore::get_all_child_dir_names(b_fs::path root, vector<string>& nodes)
+{
+ if (!b_fs::exists(root) || !b_fs::is_directory(root)) {
+ // not a valid root. nop.
+ return;
+ }
+ b_fs::directory_iterator di(root);
+ for (; di != b_fs::directory_iterator(); ++di) {
+ // must be directory
+ if (!b_fs::is_directory(di->path())) {
+ continue;
+ }
+ // name cannot start with "."
+ if (di->path().file_string().substr(0, 1) == ".") {
+ continue;
+ }
+ // found one
+ nodes.push_back(_unescape_path_name(di->path().filename()));
+ }
+}
+
+bool
+UnionfsCstore::write_file(const string& file, const string& data)
+{
+ try {
+ // make sure the path exists
+ b_fs::path fpath(file);
+ b_fs::create_directories(fpath.parent_path());
+
+ // write the file
+ ofstream fout;
+ fout.exceptions(ofstream::failbit | ofstream::badbit);
+ fout.open(file.c_str(), ios_base::out | ios_base::trunc);
+ fout << data;
+ fout.close();
+ } catch (...) {
+ return false;
+ }
+ return true;
+}
+
+bool
+UnionfsCstore::read_whole_file(const b_fs::path& fpath, string& data)
+{
+ /* must exist, be a regular file, and smaller than limit (we're going
+ * to read the whole thing).
+ */
+ if (!b_fs::exists(fpath) || !b_fs::is_regular(fpath)
+ || b_fs::file_size(fpath) > MAX_FILE_READ_SIZE) {
+ return false;
+ }
+ stringbuf sbuf;
+ ifstream fin(fpath.file_string().c_str());
+ fin >> &sbuf;
+ fin.close();
+ /* note: if file contains just a newline => (eof() && fail())
+ * so only checking bad() and eof() (we want whole file).
+ */
+ if (fin.bad() || !fin.eof()) {
+ // read failed
+ return false;
+ }
+ data = sbuf.str();
+ return true;
+}
+
+/* return whether specified "commited marker" exists in the
+ * "committed marker file".
+ */
+bool
+UnionfsCstore::committed_marker_exists(const string& marker)
+{
+ bool ret = false;
+ ifstream fin(C_COMMITTED_MARKER_FILE.c_str());
+ while (!fin.eof() && !fin.bad() && !fin.fail()) {
+ string line;
+ getline(fin, line);
+ if (line == marker) {
+ ret = true;
+ break;
+ }
+ }
+ fin.close();
+ return ret;
+}
+
+/* recursively copy source directory to destination.
+ * will throw exception (from b_fs) if fail.
+ */
+void
+UnionfsCstore::recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst)
+{
+ string src_str = src.file_string();
+ string dst_str = dst.file_string();
+ b_fs::create_directory(dst);
+
+ b_fs::recursive_directory_iterator di(src);
+ for (; di != b_fs::recursive_directory_iterator(); ++di) {
+ b_fs::path opath = di->path();
+ string nname = opath.file_string();
+ nname.replace(0, src_str.length(), dst_str);
+ b_fs::path npath = nname;
+ if (b_fs::is_directory(opath)) {
+ b_fs::create_directory(npath);
+ } else {
+ b_fs::copy_file(opath, npath);
+ }
+ }
+}
+
diff --git a/src/cstore/unionfs/cstore-unionfs.hpp b/src/cstore/unionfs/cstore-unionfs.hpp
new file mode 100644
index 0000000..357e307
--- /dev/null
+++ b/src/cstore/unionfs/cstore-unionfs.hpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2010 Vyatta, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CSTORE_UNIONFS_H_
+#define _CSTORE_UNIONFS_H_
+#include <vector>
+#include <string>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <boost/filesystem.hpp>
+
+#include <cstore/cstore.hpp>
+
+extern "C" {
+#include <cli_val.h>
+#include <cli_val_engine.h>
+}
+
+namespace b_fs = boost::filesystem;
+
+class UnionfsCstore : public Cstore {
+public:
+ UnionfsCstore(bool use_edit_level = false);
+ UnionfsCstore(const string& session_id, string& env);
+ ~UnionfsCstore();
+
+ ////// public virtual functions declared in base class
+ bool markSessionUnsaved();
+ bool unmarkSessionUnsaved();
+ bool sessionUnsaved();
+ bool sessionChanged();
+ bool setupSession();
+ bool teardownSession();
+ bool inSession();
+
+private:
+ // constants
+ static const string C_ENV_TMPL_ROOT;
+ static const string C_ENV_WORK_ROOT;
+ static const string C_ENV_ACTIVE_ROOT;
+ static const string C_ENV_CHANGE_ROOT;
+ static const string C_ENV_TMP_ROOT;
+
+ static const string C_DEF_TMPL_ROOT;
+ static const string C_DEF_CFG_ROOT;
+ static const string C_DEF_ACTIVE_ROOT;
+ static const string C_DEF_CHANGE_PREFIX;
+ static const string C_DEF_WORK_PREFIX;
+ static const string C_DEF_TMP_PREFIX;
+
+ static const string C_MARKER_DEF_VALUE;
+ static const string C_MARKER_DEACTIVATE;
+ static const string C_MARKER_CHANGED;
+ static const string C_MARKER_UNSAVED;
+ static const string C_COMMITTED_MARKER_FILE;
+ static const string C_COMMENT_FILE;
+
+ static const size_t MAX_FILE_READ_SIZE = 8192;
+
+ // root dirs (constant)
+ b_fs::path work_root; // working root (union)
+ b_fs::path active_root; // active root (readonly part of union)
+ b_fs::path change_root; // change root (r/w part of union)
+ b_fs::path tmp_root; // temp root
+ b_fs::path tmpl_root; // template root
+
+ // path buffers
+ b_fs::path mutable_cfg_path; // mutable part of config path
+ b_fs::path tmpl_path; // whole template path
+ map<const void *, pair<b_fs::path, b_fs::path> > saved_paths;
+ // saved mutable part of cfg path and whole template path
+
+ ////// virtual functions defined in base class
+ // begin path modifiers
+ void push_tmpl_path(const string& new_comp) {
+ push_path(tmpl_path, new_comp);
+ };
+ void push_tmpl_path_tag() {
+ push_tmpl_path(TAG_NAME);
+ };
+ string pop_tmpl_path() {
+ return pop_path(tmpl_path);
+ };
+ void push_cfg_path(const string& new_comp) {
+ push_path(mutable_cfg_path, new_comp);
+ };
+ void push_cfg_path_val() {
+ push_cfg_path(VAL_NAME);
+ };
+ string pop_cfg_path() {
+ return pop_path(mutable_cfg_path);
+ };
+ void append_cfg_path(const vector<string>& path_comps) {
+ for (unsigned int i = 0; i < path_comps.size(); i++) {
+ push_cfg_path(path_comps[i]);
+ }
+ };
+ void reset_paths() {
+ tmpl_path = tmpl_root;
+ mutable_cfg_path = "";
+ };
+ void save_paths(const void *handle = NULL) {
+ pair<b_fs::path, b_fs::path> p;
+ p.first = mutable_cfg_path;
+ p.second = tmpl_path;
+ saved_paths[handle] = p;
+ };
+ void restore_paths(const void *handle = NULL) {
+ map<const void *, pair<b_fs::path, b_fs::path> >::iterator it
+ = saved_paths.find(handle);
+ if (it == saved_paths.end()) {
+ exit_internal("restore_paths: handle not found\n");
+ }
+ pair<b_fs::path, b_fs::path> p = saved_paths[handle];
+ mutable_cfg_path = p.first;
+ tmpl_path = p.second;
+ };
+ bool cfg_path_at_root() {
+ return (!mutable_cfg_path.has_parent_path());
+ };
+ bool tmpl_path_at_root() {
+ return (tmpl_path.file_string() == tmpl_root.file_string());
+ };
+ // end path modifiers
+
+ // these operate on current tmpl path
+ bool tmpl_node_exists();
+ bool tmpl_parse(vtw_def& def);
+
+ // these operate on current work path
+ bool add_node();
+ bool remove_node();
+ void get_all_child_node_names_impl(vector<string>& cnodes, bool active_cfg);
+ void get_all_tmpl_child_node_names(vector<string>& cnodes) {
+ get_all_child_dir_names(tmpl_path, cnodes);
+ };
+ bool write_value_vec(const vector<string>& vvec, bool active_cfg);
+ bool rename_child_node(const string& oname, const string& nname);
+ bool copy_child_node(const string& oname, const string& nname);
+ bool mark_display_default();
+ bool unmark_display_default();
+ bool mark_deactivated();
+ bool unmark_deactivated();
+ bool unmark_deactivated_descendants();
+ bool mark_changed();
+ bool remove_comment();
+ bool set_comment(const string& comment);
+ bool discard_changes(unsigned long long& num_removed);
+
+ // observers for work path
+ bool marked_changed();
+ bool marked_display_default();
+
+ // observers for work path or active path
+ bool cfg_node_exists(bool active_cfg);
+ bool read_value_vec(vector<string>& vvec, bool active_cfg);
+ bool marked_deactivated(bool active_cfg);
+ bool get_comment(string& comment, bool active_cfg);
+
+ // observers during commit operation
+ bool marked_committed(const vtw_def& def, bool is_set);
+
+ // these operate on both current tmpl and work paths
+ bool validate_val_impl(vtw_def *def, char *value);
+
+ // observers for "edit/tmpl levels" (for "edit"-related operations).
+ // note that these should be moved to base class in the future.
+ string get_edit_level_path() {
+ return cfg_path_to_str();
+ };
+ string get_tmpl_level_path() {
+ return tmpl_path_to_str();
+ };
+ void get_edit_level(vector<string>& path_comps);
+ bool edit_level_at_root() {
+ return cfg_path_at_root();
+ };
+
+ // for testing/debugging
+ string cfg_path_to_str();
+ string tmpl_path_to_str();
+
+ ////// private functions
+ b_fs::path get_work_path() { return (work_root / mutable_cfg_path); };
+ b_fs::path get_active_path() { return (active_root / mutable_cfg_path); };
+ b_fs::path get_change_path() { return (change_root / mutable_cfg_path); };
+ void push_path(b_fs::path& old_path, const string& new_comp);
+ string pop_path(b_fs::path& path);
+ void get_all_child_dir_names(b_fs::path root, vector<string>& nodes);
+ bool write_file(const string& file, const string& data);
+ bool create_file(const string& file) {
+ return write_file(file, "");
+ };
+ bool read_whole_file(const b_fs::path& file, string& data);
+ bool committed_marker_exists(const string& marker);
+ void recursive_copy_dir(const b_fs::path& src, const b_fs::path& dst);
+};
+
+#endif /* _CSTORE_UNIONFS_H_ */
+
diff --git a/src/delete.c b/src/delete.c
deleted file mode 100644
index f2d3d31..0000000
--- a/src/delete.c
+++ /dev/null
@@ -1,391 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdarg.h>
-#include <sys/stat.h>
-#include <dirent.h>
-
-#include "cli_val.h"
-#include "cli_objects.h"
-
-static void remove_rf(boolean do_umount)
-{
- char *command = NULL;
- touch();
- if (do_umount) {
- command = malloc(strlen(get_mdirp()) + 20);
- sprintf(command, "sudo umount %s", get_mdirp());
- system(command);
- }
-
- command = realloc(command, strlen(m_path.path) + 10);
- sprintf(command, "rm -rf %s", m_path.path);
- system(command);
-
- if (do_umount) {
- command = realloc(command, strlen(get_mdirp()) + strlen(get_cdirp()) +
- strlen(get_mdirp()) + 100);
- sprintf(command, "sudo mount -t $UNIONFS -o dirs=%s=rw:%s=ro"
- " $UNIONFS %s", get_cdirp(), get_adirp(), get_mdirp());
- system(command);
- }
- free(command);
-}
-
-static boolean has_default(char **def, int size)
-{
- char *buf_ptr;
- FILE *fp = fopen(t_path.path, "r");
-
- if (fp) {
- char buf[1025];
- while (fgets(buf, 1024, fp)) {
- if (strncmp(buf, "default:", 8) == 0) {
- buf_ptr = index(buf,':');
- if (buf_ptr == NULL) {
- break;
- }
-
- //iterate up to non-whitespace character
- buf_ptr++;
- while (buf_ptr < (buf + size)) {
- if (*buf_ptr == ' ') {
- buf_ptr++;
- }
- else {
- break;
- }
- }
-
- if (size < strlen(buf_ptr)-1) {
- bye("default buffer size is too small\n");
- }
- memcpy(*def, buf_ptr, strlen(buf_ptr)-1);
- fclose(fp);
- return 0;
- }
- }
- fclose(fp);
- }
-
- return 1;
-}
-
-static void reset_default(const char *def_val)
-{
- if (def_val == NULL)
- return;
-
- //strip off quotes
- char tmp_val[1025];
- char *ptr = index(def_val,'"');
- if (ptr != NULL) {
- strcpy(tmp_val,ptr+1);
- ptr = rindex(tmp_val,'"');
- if (ptr != NULL) {
- *ptr = '\0';
- }
- else {
- strcpy(tmp_val,def_val); //go with original value.
- }
- }
- else {
- strcpy(tmp_val,def_val);
- }
-
- char filename[strlen(m_path.path) + 10];
- touch();
- sprintf(filename, "%s/node.val", m_path.path);
-
- FILE *fp = fopen(filename, "w");
- if (fp == NULL)
- bye("can not open: %s", filename);
- fputs(tmp_val, fp);
- fclose(fp);
-
- sprintf(filename, "%s/def", m_path.path);
- touch_file(filename);
-}
-/***************************************************
- set_validate:
- validate value against definition
- return TRUE if OK, FALSE otherwise
-****************************************************/
-boolean set_validate(vtw_def *defp, char *valp)
-{
- boolean res;
- int status;
- struct stat statbuf;
-
- pop_path(&t_path); /* it was tag or real value */
- push_path(&t_path, DEF_NAME);
- if (lstat(t_path.path, &statbuf) < 0 ||
- (statbuf.st_mode & S_IFMT) != S_IFREG) {
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("Can not set value (2), no definition for %s", m_path.path);
- }
- /* defniition present */
- if ((status = parse_def(defp, t_path.path, FALSE))) {
- bye("parse error: %d\n", status);
- }
- res = validate_value(defp, valp);
- pop_path(&t_path);
- return res;
-}
-
-int main(int argc, char **argv)
-{
- int ai;
- struct stat statbuf;
- vtw_def def;
- boolean last_tag=0;
- int status;
- FILE *fp;
- boolean res;
- char *cp, *delp, *endp;
- boolean do_umount;
-
- /* this is needed before calling certain glib functions */
- g_type_init();
-
- if (initialize_output("Delete") == -1) {
- bye("can't initialize output\n");
- }
-
- if (argc < 2) {
- fprintf(out_stream, "Need to specify the config node to delete\n");
- bye("nothing to delete\n");
- }
-
- dump_log( argc, argv);
- do_umount = FALSE;
- init_edit();
- /* extend both paths per arguments given */
- /* last argument is new value */
- for (ai = 1; ai < argc; ++ai) {
- push_path(&t_path, argv[ai]);
- push_path(&m_path, argv[ai]);
- if (lstat(t_path.path, &statbuf) >= 0) {
- if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
- bye("INTERNAL:regular file %s in templates", t_path.path);
- }
- last_tag = FALSE;
- continue;
- } /*else */
- pop_path(&t_path);
- push_path(&t_path, TAG_NAME);
- if (lstat(t_path.path, &statbuf) >= 0) {
- if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
- bye("INTERNAL:regular file %s in templates", t_path.path);
- }
- last_tag = TRUE;
- continue;
- }
- /* no match */
- break;
- }
- /*
- cases:
- multiple tag-value - not achild
- mutilple tag-value - not the last child
- multiple tag-value - last child
- single value modified
- signle value unmodified
- multiple non-tag value - the last value
- multiple non-tag value - not the last value
- regular child
- */
- if (ai == argc) {
- /* full path found */
- /* all cases except multiple non-tag value */
- /* check for single value */
- if (last_tag) {
- /* case of multiple tag-value
- was this a real child?
- was it the last child?
- */
- struct dirent *dirp;
- DIR *dp;
-
- if (lstat(m_path.path, &statbuf) < 0) {
- fprintf(out_stream, "Nothing to delete\n");
- bye("Nothing to delete at %s", m_path.path);
- }
-
- remove_rf(FALSE);
- pop_path(&m_path);
- if ((dp = opendir(m_path.path)) == NULL){
- INTERNAL;
- }
- while ((dirp = readdir(dp)) != NULL) {
- /*do we have real child */
- if (strcmp(dirp->d_name, ".") &&
- strcmp(dirp->d_name, "..") &&
- strcmp(dirp->d_name, MOD_NAME) && /* XXX */
- strncmp(dirp->d_name, ".wh.", 4) )
- break;
- }
- if (dirp == NULL) {
- /* no real children left */
- /* kill parent also */
- remove_rf(FALSE);
- }
- exit(0);
- }
- /*not tag */
- push_path(&t_path, DEF_NAME);
- if (lstat(t_path.path, &statbuf) >= 0 &&
- (statbuf.st_mode & S_IFMT) == S_IFREG) {
- /* defniition present */
- if ((status = parse_def(&def, t_path.path, FALSE))) {
- bye("parse error: %d\n", status);
- }
- if (!def.tag && !def.multi && def.def_type!= ERROR_TYPE) {
- /* signgle value */
- /* is it modified ==
- it is in C, but not OPAQUE */
- switch_path(CPATH);
- if(lstat(m_path.path, &statbuf) >= 0) {
- push_path(&m_path, OPQ_NAME);
- if(lstat(m_path.path, &statbuf) < 0) {
- /* yes remove from C only */
- pop_path(&m_path);
- remove_rf(TRUE);
- exit(0);
- }
- pop_path(&m_path); /*OPQ_NAME */
- }
- switch_path(MPATH);
- }
- }
- /* else no defnition, remove it also */
- char *def_val;
- def_val = malloc(1025);
- if (has_default(&def_val,1024) == 0) {
- reset_default(def_val);
- free(def_val);
- }
- else {
- remove_rf(FALSE);
- }
-
- // remove_rf(FALSE);
- exit(0);
- }
- if(ai < argc -1 || last_tag) {
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("There is no appropriate template for %s",
- m_path.path + strlen(get_mdirp()));
- }
- /*ai == argc -1, must be actual value */
- pop_path(&m_path); /*it was value, not path segment */
- push_path(&m_path, VAL_NAME);
- /* set value */
- if (lstat(m_path.path, &statbuf) < 0) {
- fprintf(out_stream, "Nothing to delete\n");
- bye("Nothing to delete at %s", m_path.path);
- }
- if ((statbuf.st_mode & S_IFMT) != S_IFREG)
- bye("Not a regular file %s", m_path.path);
- /* get definition to deal with potential multi */
- pop_path(&t_path); /* it was tag or real value */
- push_path(&t_path, DEF_NAME);
- if (lstat(t_path.path, &statbuf) < 0 ||
- (statbuf.st_mode & S_IFMT) != S_IFREG) {
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("Can not delete value, no definition for %s", m_path.path);
- }
- /* defniition present */
- if ((status = parse_def(&def, t_path.path, FALSE))) {
- bye("parse error: %d\n", status);
- }
- if (def.multi) {
- /* delete from multivalue */
- valstruct new_value, old_value;
- status = char2val(&def, argv[argc - 1], &new_value);
- if (status)
- exit(0);
- cp = NULL;
- pop_path(&m_path); /* get_value will push VAL_NAME */
- status = get_value(&cp, &m_path);
- if (status != VTWERR_OK)
- bye("Cannot read old value %s\n", m_path.path);
- status = char2val(&def, cp, &old_value);
- if (status != VTWERR_OK)
- bye("Corrupted old value ---- \n%s\n-----\n", cp);
- res = val_cmp(&new_value, &old_value, IN_COND);
- if (!res) {
- fprintf(out_stream, "%s is not a configured value, %s\n", new_value.val,old_value.val);
- bye("Not in multivalue");
- }
- touch();
- if (old_value.cnt) {
- push_path(&m_path, VAL_NAME);
- fp = fopen(m_path.path, "w");
- if (fp == NULL)
- bye("Can not open value file %s", m_path.path);
- if (is_in_cond_tik()) {
- for(delp=cp;delp && is_in_cond_tik(); dec_in_cond_tik()) {
- delp = strchr(delp, '\n');
- if (!delp)
- INTERNAL;
- ++delp; /* over \n */
- }
- /* write "left" of deleted */
- fwrite(cp, 1, delp-cp, fp);
- }else
- delp = cp;
- /* find end of value */
- endp = strchr(delp, '\n');
- if (endp && *++endp) {
- /* write "right" of deleted */
- fwrite(endp, 1, strlen(endp), fp);
- /* need the final '\n' */
- fwrite("\n", 1, 1, fp);
- }
- fclose(fp);
- return 0;
- }
- /* it multi with only 1 value, remove */
- remove_rf(FALSE);
- return 0;
- }
-
- /*
- let's do a new check here:
- -> if this is a leaf and there is a value look for a match of the value
- -> make sure to check existing configuration as well as uncommitted config
- */
- if (ai+1 == argc) {
- //does this work up until the last value
- pop_path(&m_path);
- if(lstat(m_path.path, &statbuf) == 0) {
- //now compare last value with that in the node.def file to determine whether to accept this delete
- status = get_value(&cp, &m_path);
- if (status != VTWERR_OK) {
- bye("Cannot read old value %s\n", m_path.path);
- }
- if (!strcmp(cp,argv[argc - 1])) {
- /* Also need to handle the case where the value is not specified. */
- char *def_val;
- def_val = malloc(1025);
- if (has_default(&def_val,1024) == 0) {
- reset_default(def_val);
- free(def_val);
- }
- else {
- remove_rf(FALSE);
- }
- return 0;
- }
- }
- }
-
-
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("There is no appropriate template for %s",
- m_path.path + strlen(get_mdirp()));
-
- return 1;
-}
-
diff --git a/src/set.c b/src/set.c
deleted file mode 100644
index fb2d613..0000000
--- a/src/set.c
+++ /dev/null
@@ -1,498 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-#include <dirent.h>
-
-#include "cli_val.h"
-#include "cli_objects.h"
-#include "cli_path_utils.h"
-
-static void handle_defaults(void);
-
-static void make_dir()
-{
- mkdir_p(m_path.path);
-}
-/***************************************************
- set_validate:
- validate value against definition
- return TRUE if OK, FALSE otherwise
-****************************************************/
-boolean set_validate(vtw_def *defp, char *valp, boolean empty_val)
-{
- boolean res;
- int status;
- struct stat statbuf;
- char* path_end=NULL;
-
- if (!empty_val) {
- {
- clind_path_ref tp = clind_path_construct(t_path.path);
- if(tp) {
- path_end=clind_path_pop_string(tp);
- }
- clind_path_destruct(&tp);
- }
-
- if (strcmp(path_end, TAG_NAME) == 0) {
- /* it was a tag, so the def is at 1 level up */
- pop_path(&t_path);
- } else {
- /* it's actual value, so the tmpl path is fine */
- free(path_end);
- path_end = NULL;
- }
- }
- push_path(&t_path, DEF_NAME);
- if (lstat(t_path.path, &statbuf) < 0 ||
- (statbuf.st_mode & S_IFMT) != S_IFREG) {
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("Can not set value (4), no definition for %s, template %s",
- m_path.path,t_path.path);
- }
- /* defniition present */
- if ((status = parse_def(defp, t_path.path, FALSE))) {
- bye("parse error: %d\n", status);
- }
- pop_path(&t_path);
- if(path_end) {
- push_path(&t_path,path_end);
- free(path_end);
- path_end=NULL;
- }
- if (defp->def_type == ERROR_TYPE) {
- /* no type in def. setting value not allowed. */
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("Can not set value: no type for %s, template %s",
- m_path.path, t_path.path);
- }
- if (empty_val) {
- if (defp->def_type != TEXT_TYPE || defp->tag || defp->multi){
- printf("Empty string may be assigned only to TEXT type leaf node\n");
- fprintf(out_stream, "Empty value is not allowed\n");
- return FALSE;
- }
- return TRUE;
- }
-
- //apply limit count here, needs to be defined as a tag,multi and have a value set
-
- //NOTE: changed behavior for def_tag==1. Needs signed 32 support in parser where -1
- //represents embedded tag node... TODO
- if ((defp->tag || defp->multi) && (defp->def_tag != 0 || defp->def_multi != 0)) {
- //get count of siblings
-
- char path[2048];
- char val[2048];
- char last_val[2048];
- char *pos = rindex(m_path.path,'/');
- if (pos != NULL) {
- int offset = pos - m_path.path;
- strncpy(path,m_path.path,offset);
- path[offset] = '\0';
-
- strncpy(val,m_path.path + offset + 1, strlen(m_path.path) - offset - 1);
- val[strlen(m_path.path) - offset - 1] = '\0';
-
- // fprintf(out_stream,"val: %s, offset: %d, path: %s\n",val,offset,m_path.path);
-
- int entity_count = 0;
- if (defp->def_tag) {
- struct dirent* entry;
- DIR* dirp = opendir(path); /* There should be error handling after this */
- if (dirp != NULL) {
- while ((entry = readdir(dirp)) != NULL) {
- if (strcmp(entry->d_name,".") != 0 &&
- strcmp(entry->d_name,"..") != 0 &&
- strcmp(val,entry->d_name) != 0) {
- strcpy(last_val,entry->d_name);
- entity_count++;
- }
- }
- closedir(dirp);
- }
- }
- else if (defp->def_multi) {
- //fopen file and count number of lines
- char buf[4096];
- sprintf(buf,"%s/node.val",m_path.path);
- FILE *fp = fopen(buf,"r");
- if (fp) {
- char *line = NULL;
- size_t len = 0;
- while (getline(&line,&len,fp) != -1) {
- ++entity_count;
- }
- if (line) {
- free(line);
- }
- fclose(fp);
- }
- }
-
- if (defp->tag && entity_count == 1 && defp->def_tag < 0) {
- //this is the special case, where the previous value should be deleted here...
- char command[8192];
- //let unionfs handle the diff
- sprintf(command, "mv %s/%s %s/%s", path,last_val,path,val);
- system(command);
- }
-
- if (defp->tag) {
- if (defp->def_tag > 0 && entity_count >= defp->def_tag) {
- char *p = rindex(path,'/');
- char tmp[2048];
- if (p != NULL) {
- int off = p - path;
- strncpy(tmp,path + off + 1, strlen(path) - off - 1);
- tmp[strlen(path) - off - 1] = '\0';
- fprintf(out_stream,"Number of values exceeded for '%s', allowed: %d, actual: %d\n",tmp,defp->def_tag,entity_count);
- }
- else {
- fprintf(out_stream,"Number of values exceeded, allowed: %d, actual: %d\n",defp->def_tag,entity_count);
- }
- return FALSE;
- }
- }
- else {
- if (defp->def_multi > 0 && entity_count >= defp->def_multi) {
- fprintf(out_stream,"Number of values exceeded, allowed: %d, actual: %d\n",defp->def_multi,entity_count);
- return FALSE;
- }
- }
- }
- }
-
- res = validate_value(defp, valp);
- return res;
-}
-
-int main(int argc, char **argv)
-{
-
- int ai;
- struct stat statbuf;
- vtw_def def;
- boolean last_tag;
- int status;
- FILE *fp;
- boolean res;
- char *cp;
- boolean need_mod = FALSE, not_new = FALSE;
- boolean empty_val = FALSE;
-
- /* this is needed before calling certain glib functions */
- g_type_init();
-
- if (initialize_output("Set") == -1) {
- bye("can't initialize output\n");
- }
-
- dump_log( argc, argv);
- init_edit();
- last_tag = FALSE;
-
- /* extend both paths per arguments given */
- /* last argument is new value */
- for (ai = 1; ai < argc; ++ai) {
- if (!*argv[ai]) { /* empty string */
- if (ai < argc -1) {
- fprintf(out_stream, "Empty value is not allowed after \"%s\"\n",
- argv[ai - 1]);
- bye("empty string in argument list \n");
- }
- empty_val = TRUE;
- last_tag = FALSE;
- break;
- }
- push_path(&t_path, argv[ai]);
- push_path(&m_path, argv[ai]);
- if (lstat(t_path.path, &statbuf) >= 0) {
- if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
- bye("INTERNAL:regular file %s in templates", t_path.path);
- }
- last_tag = FALSE;
- continue;
- } /*else */
- pop_path(&t_path);
- push_path(&t_path, TAG_NAME);
- if (lstat(t_path.path, &statbuf) >= 0) {
- if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
- bye("INTERNAL:regular file %s in templates", t_path.path);
- }
- last_tag = TRUE;
- /* every time tag match, need to verify*/
- if(!set_validate(&def, argv[ai], FALSE)) {
- bye("value \"%s\" is not valid\n", argv[ai]);
- }
- continue;
- }
- /* no match */
- break;
- }
-
- if (ai == argc) {
- /* full path found */
- /* every tag match validated already */
- /* non tag matches are OK by definition */
- /* do we already have it? */
- if (lstat(m_path.path, &statbuf) >= 0) {
- printf("Node [%s] already exists\n", m_path.path + strlen(get_mdirp()));
- /* not an error */
- exit(0);
- }
- /* else */
- /* prevent value node without actual value */
- push_path(&t_path, DEF_NAME);
- if (lstat(t_path.path, &statbuf) >= 0) {
- if ((status = parse_def(&def, t_path.path, FALSE))) {
- bye("parse error: %d\n", status);
- }
- if (def.def_type != ERROR_TYPE && !def.tag) {
- fprintf(out_stream,
- "The specified configuration node requires a value\n");
- bye("Must provide actual value\n");
- }
- if (def.def_type == ERROR_TYPE && !def.tag) {
- pop_path(&t_path);
- if(!validate_value(&def, "")) {
- bye("validate_value failed\n");
- }
- push_path(&t_path, DEF_NAME);
- }
- }
- touch();
- pop_path(&t_path);
- make_dir();
- handle_defaults();
- exit(0);
- }
- if(ai < argc -1 || last_tag) {
- fprintf(out_stream, "The specified configuration node is not valid\n");
- bye("There is no appropriate template for %s",
- m_path.path + strlen(get_mdirp()));
- }
- /*ai == argc -1, must be actual value */
- if (!empty_val) {
- pop_path(&m_path); /* pop the actual value at the end */
- pop_path(&t_path); /* pop the "node.tag" */
- }
- handle_defaults();
-
- if(!set_validate(&def, argv[argc-1], empty_val)) {
- bye("value \"%s\" is not valid\n", argv[argc - 1]);
- }
- push_path(&m_path, VAL_NAME);
- /* set value */
- if (lstat(m_path.path, &statbuf) >= 0) {
- valstruct new_value, old_value;
- not_new = TRUE;
- if ((statbuf.st_mode & S_IFMT) != S_IFREG)
- bye("Not a regular file at path \"%s\"", m_path.path);
- /* check if this new value */
- status = char2val(&def, argv[argc - 1], &new_value);
- if (status)
- exit(0);
- cp = NULL;
- pop_path(&m_path); /* get_value will push VAL_NAME */
- status = get_value(&cp, &m_path);
- if (status != VTWERR_OK)
- bye("Cannot read old value %s\n", m_path.path);
- status = char2val(&def, cp, &old_value);
- if (status != VTWERR_OK)
- bye("Corrupted old value ---- \n%s\n-----\n", cp);
- res = val_cmp(&new_value, &old_value, IN_COND);
- if (res) {
- if (def.multi) {
- printf("Already in multivalue\n");
- } else {
- printf("The same value \"%s\" for path \"%s\"\n", cp, m_path.path);
- }
- /* not treating as error */
- exit(0);
- }
- } else {
- pop_path(&m_path);
- }
- make_dir();
-
-
- if (!def.multi) {
- char path[strlen(m_path.path)+5];
- sprintf(path, "%s/def",m_path.path);
- unlink(path);
- }
-
- push_path(&m_path, VAL_NAME);
- if(not_new && !def.multi) {
- /* it is not multi and seen from M */
- /* is it in C */
- switch_path(CPATH);
- if (lstat(m_path.path, &statbuf) < 0)
- /* yes, we are modifying original value */
- need_mod = TRUE;
- switch_path(MPATH);
- }
- touch();
- /* in case of multi we always append, never overwrite */
- /* in case of single we always overwrite */
- /* append and overwrite work the same for new file */
- fp = fopen(m_path.path, def.multi?"a":"w");
- if (fp == NULL)
- bye("Can not open value file %s", m_path.path);
- if (fputs(argv[argc-1], fp) < 0 || fputc('\n',fp) < 0)
- bye("Error writing file %s", m_path.path);
- if (need_mod) {
- pop_path(&m_path); /* get rid of "value" */
- char filename[strlen(m_path.path) + sizeof(MOD_NAME)+1];
- sprintf(filename, "%s/" MOD_NAME, m_path.path);
- touch_file(filename);
- }
- return 0;
-}
-
-static char *
-last_path_segment(const char *path)
-{
- char *tmp = strrchr(path, '/');
- return ((tmp) ? (tmp + 1) : tmp);
-}
-
-/* handle_default(mpath, tpath, exclude)
- * create any nodes with default values at the current level.
- * mpath: working path
- * tpath: template path
- * exclude: path to exclude
- */
-static void
-handle_default(vtw_path *mpath, vtw_path *tpath, char *exclude)
-{
- DIR *dp;
- struct dirent *dirp;
- char *uename = NULL;
- struct stat statbuf;
- vtw_def def;
- int status;
- FILE *fp;
-
- if ((dp = opendir(tpath->path)) == NULL) {
- perror("handle_default: opendir");
- bye("opendir failed\n");
- }
-
- while ((dirp = readdir(dp)) != NULL) {
- if (strcmp(dirp->d_name, ".") == 0
- || strcmp(dirp->d_name, "..") == 0
- || strcmp(dirp->d_name, MOD_NAME) == 0
- || strcmp(dirp->d_name, DEF_NAME) == 0
- || strcmp(dirp->d_name, TAG_NAME) == 0
- || strcmp(dirp->d_name, exclude) == 0) {
- continue;
- }
- if (uename) {
- free(uename);
- uename = NULL;
- }
- uename = clind_unescape(dirp->d_name);
- push_path(tpath, uename);
- if (lstat(tpath->path, &statbuf) < 0) {
- fprintf(stderr, "no template directory [%s]\n", tpath->path);
- pop_path(tpath);
- continue;
- }
- if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
- fprintf(stderr, "non-directory [%s]\n", tpath->path);
- pop_path(tpath);
- continue;
- }
- push_path(tpath, DEF_NAME);
- if (lstat(tpath->path, &statbuf) < 0) {
- /* no definition */
- pop_path(tpath); /* definition */
- pop_path(tpath); /* child */
- continue;
- }
- if ((status = parse_def(&def, tpath->path, FALSE))) {
- /* template parse error. abort. */
- bye("Parse error in [%s]\n", tpath->path);
- }
- if (def.def_default) {
- push_path(mpath, uename);
-
- push_path(mpath, VAL_NAME);
- if (lstat(mpath->path, &statbuf) < 0) {
- /* no value. add the default */
- pop_path(mpath);
- touch_dir(mpath->path); /* make sure directory exist */
-
- //create def marker
- char def_file[strlen(mpath->path)+8];
- sprintf(def_file,"%s/def",mpath->path);
- fp = fopen(def_file, "w");
- if (fp == NULL)
- bye("Can not open def file %s", def_file);
- fputs("empty\n", fp);
- fclose(fp);
-
- push_path(mpath, VAL_NAME);
- fp = fopen(mpath->path, "w");
- if (fp == NULL) {
- bye("Can not open value file %s", mpath->path);
- }
- if (fputs(def.def_default, fp) < 0
- || fputc('\n',fp) < 0) {
- bye("Error writing file %s", mpath->path);
- }
- fclose(fp);
-
- }
- pop_path(mpath); /* value */
- pop_path(mpath); /* child */
- }
- free_def(&def);
- pop_path(tpath); /* definition */
- pop_path(tpath); /* child */
- }
- if (uename) {
- free(uename);
- }
- closedir(dp);
-}
-
-/* handle_defaults()
- * create any nodes with default values along the current "global"
- * configuration/template path (m_path/t_path).
- */
-static void
-handle_defaults()
-{
- vtw_path mpath;
- vtw_path tpath;
- char *path_end = strdup("");
-
- memset(&mpath, 0, sizeof(mpath));
- memset(&tpath, 0, sizeof(tpath));
- copy_path(&mpath, &m_path);
- copy_path(&tpath, &t_path);
-
- while (mpath.path_lev > 0) {
- handle_default(&mpath, &tpath, path_end);
-
- if (mpath.path_lev == 1) {
- break;
- }
-
- free(path_end);
- path_end = strdup(last_path_segment(tpath.path));
- pop_path(&mpath);
- pop_path(&tpath);
- }
-
- free(path_end);
- free_path(&mpath);
- free_path(&tpath);
-}
-