diff options
Diffstat (limited to 'src/cstore/unionfs/cstore-unionfs.cpp')
-rw-r--r-- | src/cstore/unionfs/cstore-unionfs.cpp | 1078 |
1 files changed, 1078 insertions, 0 deletions
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); + } + } +} + |