/* * Copyright (C) 2010 Vyatta, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include ////// 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"; const string UnionfsCstore::C_TAG_NAME = "node.tag"; const string UnionfsCstore::C_VAL_NAME = "node.val"; const string UnionfsCstore::C_DEF_NAME = "node.def"; ////// static static Cstore::MapT _fs_escape_chars; static Cstore::MapT _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) { Cstore::MapT::iterator p = _fs_escape_chars.find(c); if (p != _fs_escape_chars.end()) { return _fs_escape_chars[c]; } else { return string(1, c); } } static Cstore::MapT _escape_path_name_cache; static string _escape_path_name(const string& path) { Cstore::MapT::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 (size_t i = 0; i < path.size(); i++) { npath += _escape_char(path[i]); } // cache it before return _escape_path_name_cache[path] = npath; return npath; } static Cstore::MapT _unescape_path_name_cache; static string _unescape_path_name(const string& path) { Cstore::MapT::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 (size_t i = 0; i < path.size(); i++) { if ((path.size() - i) < 3) { npath += path.substr(i); break; } string s = path.substr(i, 3); Cstore::MapT::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. */ mutable_cfg_path = "/"; 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; mutable_cfg_path = "/"; 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(C_DEF_NAME); bool ret = (b_fs_exists(tmpl_path) && b_fs_is_regular(tmpl_path) && parse_def(&def, tmpl_path.file_string().c_str(), 0) == 0); pop_tmpl_path(); return ret; } bool UnionfsCstore::cfg_node_exists(bool active_cfg) { b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); return (b_fs_exists(p) && b_fs_is_directory(p)); } bool UnionfsCstore::add_node() { bool ret = true; try { if (!b_fs::create_directory(get_work_path())) { // already exists. shouldn't call this function. ret = false; } } catch (...) { ret = false; } if (!ret) { output_internal("failed to add node [%s]\n", get_work_path().file_string().c_str()); } return ret; } bool UnionfsCstore::remove_node() { if (!b_fs_exists(get_work_path()) || !b_fs_is_directory(get_work_path())) { output_internal("remove non-existent node [%s]\n", get_work_path().file_string().c_str()); return false; } bool ret = false; try { if (b_fs::remove_all(get_work_path()) != 0) { ret = true; } } catch (...) { ret = false; } if (!ret) { output_internal("failed to remove node [%s]\n", get_work_path().file_string().c_str()); } return ret; } void UnionfsCstore::get_all_child_node_names_impl(vector& cnodes, bool active_cfg) { b_fs::path p = (active_cfg ? get_active_path() : get_work_path()); get_all_child_dir_names(p, cnodes); /* XXX special cases to emulate original perl API behavior. * original perl listNodes() and listOrigNodes() return everything * under a node (except for ".*"), including "node.val" and "def". * * perl API should operate at abstract level and should not access * such implementation-specific details. however, currently * things like config output depend on this behavior, so this * function needs to return them for now. * * use a whilelist-approach, i.e., only add the following: * node.val * def * * FIXED: perl scripts have been changed to eliminate the use of "def" * and "node.val", so they no longer need to be returned. */ } bool UnionfsCstore::read_value_vec(vector& vvec, bool active_cfg) { push_cfg_path_val(); b_fs::path vpath = (active_cfg ? get_active_path() : get_work_path()); bool ret = false; do { string ostr; if (!read_whole_file(vpath, ostr)) { break; } /* XXX original implementation used to remove a trailing '\n' after * a read. it was only necessary because it was adding a '\n' when * writing the file. don't remove anything now since we shouldn't * be writing it any more. */ // separate values using newline as delimiter size_t start_idx = 0, idx = 0; for (; idx < ostr.size(); idx++) { if (ostr[idx] == '\n') { // got a value vvec.push_back(ostr.substr(start_idx, (idx - start_idx))); start_idx = idx + 1; } } if (start_idx < ostr.size()) { vvec.push_back(ostr.substr(start_idx, (idx - start_idx))); } else { // last char is a newline => another empty value vvec.push_back(""); } ret = true; } while (0); pop_cfg_path(); return ret; } bool UnionfsCstore::write_value_vec(const vector& vvec, bool active_cfg) { push_cfg_path_val(); bool ret = false; b_fs::path wp = (active_cfg ? get_active_path() : get_work_path()); do { if (b_fs_exists(wp) && !b_fs_is_regular(wp)) { // not a file break; } string ostr = ""; for (size_t 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(bool active_cfg) { b_fs::path marker = (active_cfg ? get_active_path() : 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; } try { vector markers; b_fs::recursive_directory_iterator di(get_work_path()); for (; di != b_fs::recursive_directory_iterator(); ++di) { if (!b_fs_is_regular(di->path()) || di->path().filename() != C_MARKER_DEACTIVATE) { // not marker continue; } if (di->path().parent_path() == get_work_path()) { // don't unmark the node itself continue; } markers.push_back(di->path()); } for (size_t 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; } // mark current work path and all ancestors as "changed" bool UnionfsCstore::mark_changed_with_ancestors() { b_fs::path opath = mutable_cfg_path; // use a copy bool done = false; while (!done) { b_fs::path marker = work_root; if (opath.has_parent_path()) { marker /= opath; pop_path(opath); } else { done = true; } if (!b_fs_exists(marker) || !b_fs_is_directory(marker)) { // don't do anything if the node is not there continue; } marker /= C_MARKER_CHANGED; if (b_fs_exists(marker)) { // reached a node already marked => done break; } if (!create_file(marker.file_string())) { output_internal("failed to mark changed [%s]\n", marker.file_string().c_str()); return false; } } return true; } /* remove all "changed" markers under the current work path. this is used, * e.g., at the end of "commit" to reset a subtree. */ bool UnionfsCstore::unmark_changed_with_descendants() { try { vector markers; b_fs::recursive_directory_iterator di(get_work_path()); for (; di != b_fs::recursive_directory_iterator(); ++di) { if (!b_fs_is_regular(di->path()) || di->path().filename() != C_MARKER_CHANGED) { // not marker continue; } markers.push_back(di->path()); } for (size_t i = 0; i < markers.size(); i++) { b_fs::remove(markers[i]); } } catch (...) { output_internal("failed to unmark changed with descendants [%s]\n", get_work_path().file_string().c_str()); return false; } return true; } // remove the comment at the current work path bool UnionfsCstore::remove_comment() { b_fs::path cfile = get_work_path() / C_COMMENT_FILE; if (!b_fs_exists(cfile)) { return false; } try { b_fs::remove(cfile); } catch (...) { output_internal("failed to remove comment [%s]\n", cfile.file_string().c_str()); return false; } return true; } // set comment at the current work path bool UnionfsCstore::set_comment(const string& comment) { b_fs::path cfile = get_work_path() / C_COMMENT_FILE; return write_file(cfile.file_string(), comment); } // discard all changes in working config bool UnionfsCstore::discard_changes(unsigned long long& num_removed) { // unionfs-specific implementation vector files; vector directories; try { // iterate through all entries in change root b_fs::directory_iterator di(change_root); for (; di != b_fs::directory_iterator(); ++di) { if (b_fs_is_directory(di->path())) { directories.push_back(di->path()); } else { files.push_back(di->path()); } } // remove and count num_removed = 0; for (size_t i = 0; i < files.size(); i++) { b_fs::remove(files[i]); num_removed++; } for (size_t 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); } // whether current work path is "changed" bool UnionfsCstore::cfg_node_changed() { b_fs::path marker = get_work_path() / C_MARKER_CHANGED; return b_fs_exists(marker); } /* XXX currently "committed marking" is done inside commit. * TODO move "committed marking" out of commit and into low-level * implementation (here). */ /* return whether current "cfg path" has been committed, i.e., whether * the set or delete operation on the path has been processed by commit. * is_set: whether the operation is set (for sanity check as there can * be only one operation on the path). */ bool UnionfsCstore::marked_committed(const vtw_def& def, bool is_set) { b_fs::path cpath = mutable_cfg_path; string com_str = cpath.file_string() + "/"; if (def.is_value && !def.tag) { // path includes leaf value. construct the right string. string val = _unescape_path_name(cpath.filename()); cpath = cpath.parent_path(); /* XXX current commit implementation escapes value strings for * single-value nodes but not for multi-value nodes for some * reason. the following match current behavior. */ if (!def.multi) { val = _escape_path_name(val); } com_str = cpath.file_string() + "/value:" + val; } com_str = (is_set ? "+ " : "- ") + com_str; return committed_marker_exists(com_str); } bool UnionfsCstore::validate_val_impl(vtw_def *def, char *value) { /* XXX filesystem paths/accesses are completely embedded in var ref lib. * for now, treat the lib as a unionfs-specific implementation. * generalizing it will need a rewrite. * set the handle to be used during validate_value() for var ref * processing. this is a global var in cli_new.c. */ var_ref_handle = (void *) this; bool ret = validate_value(def, value); var_ref_handle = NULL; return ret; } void UnionfsCstore::get_edit_level(vector& pcomps) { b_fs::path opath = mutable_cfg_path; // use a copy while (opath.has_parent_path()) { pcomps.insert(pcomps.begin(), pop_path(opath)); } } string UnionfsCstore::cfg_path_to_str() { string cpath = mutable_cfg_path.file_string(); if (cpath.length() == 0) { cpath = "/"; } return cpath; } string UnionfsCstore::tmpl_path_to_str() { // return only the mutable part string tpath = tmpl_path.file_string(); tpath.erase(0, tmpl_root.file_string().length()); if (tpath.length() == 0) { tpath = "/"; } return tpath; } ////// private functions void UnionfsCstore::push_path(b_fs::path& old_path, const string& new_comp) { string comp = _escape_path_name(new_comp); old_path /= comp; } string UnionfsCstore::pop_path(b_fs::path& path) { string ret = _unescape_path_name(path.filename()); /* note: contrary to documentation, remove_filename() does not remove * trailing slash. */ path = path.parent_path(); return ret; } void UnionfsCstore::get_all_child_dir_names(b_fs::path root, vector& nodes) { if (!b_fs_exists(root) || !b_fs_is_directory(root)) { // not a valid root. nop. return; } try { b_fs::directory_iterator di(root); for (; di != b_fs::directory_iterator(); ++di) { // must be directory if (!b_fs_is_directory(di->path())) { continue; } // name cannot start with "." string cname = di->path().filename(); if (cname.length() < 1 || cname[0] == '.') { continue; } // found one nodes.push_back(_unescape_path_name(cname)); } } catch (...) { return; } } 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)) { return false; } try { if (b_fs::file_size(fpath) > MAX_FILE_READ_SIZE) { return false; } stringbuf sbuf; ifstream fin(fpath.file_string().c_str()); fin >> &sbuf; fin.close(); /* note: if file contains just a newline => (eof() && fail()) * so only checking bad() and eof() (we want whole file). */ if (fin.bad() || !fin.eof()) { // read failed return false; } data = sbuf.str(); } catch (...) { return false; } return true; } /* return whether specified "commited marker" exists in the * "committed marker file". */ bool UnionfsCstore::committed_marker_exists(const string& marker) { bool ret = false; try { ifstream fin(C_COMMITTED_MARKER_FILE.c_str()); while (!fin.eof() && !fin.bad() && !fin.fail()) { string line; getline(fin, line); if (line == marker) { ret = true; break; } } fin.close(); } catch (...) { ret = false; } 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); } } }