/* * 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 using namespace cstore; ////// 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] == '/'); vector tmp; while (!_absolute && !_cstore->cfg_path_at_root()) { string last; _cstore->pop_cfg_path(last); tmp.push_back(last); } while (tmp.size() > 0) { _orig_path_comps.push(tmp.back()); tmp.pop_back(); } _cstore->reset_paths(true); /* 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; Cpath rcomps; while (si < ref_str.length() && (sn = ref_str.find('/', si)) != ref_str.npos) { rcomps.push(ref_str.substr(si, sn - si)); si = sn + 1; } if (si < ref_str.length()) { rcomps.push(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 Cpath 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 Cpath& ref_comps, const Cpath& cur_path_comps, vtw_type_e def_type) { if (ref_comps.size() == 0) { // done _paths.push_back(pair(cur_path_comps, def_type)); return; } Cpath rcomps; Cpath pcomps(cur_path_comps); string cr_comp = ref_comps[0]; for (size_t i = 1; i < ref_comps.size(); i++) { rcomps.push(ref_comps[i]); } tr1::shared_ptr def(_cstore->parseTmpl(pcomps, false)); bool got_tmpl = (def.get() != 0); bool handle_leaf = false; if (cr_comp == "@") { if (!got_tmpl) { // invalid path return; } if (def->isTypeless()) { /* no value for typeless node, so this should be invalid ref * according to the spec. * XXX however, the original implementation erroneously treats * this as valid ref and returns the node "name" as the "value". * for backward compatibility, keep the same behavior. */ process_ref(rcomps, pcomps, ERROR_TYPE); return; } if (pcomps.size() == _orig_path_comps.size()) { if (pcomps.size() == 0 || pcomps == _orig_path_comps) { /* we are at the original path. this is a self-reference, e.g., * $VAR(@), so use the "at string". */ pcomps.push(_at_string); process_ref(rcomps, pcomps, def->getType(1)); return; } } if (!def->isSingleLeafNode() && !def->isMultiLeafNode()) { if (pcomps.size() < _orig_path_comps.size()) { // within the original path. @ translates to the path comp. pcomps.push(_orig_path_comps[pcomps.size()]); process_ref(rcomps, pcomps, def->getType(1)); } 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(); if (pcomps.size() > 0) { // not at root yet def = _cstore->parseTmpl(pcomps, false); if (!def.get()) { // invalid tmpl path return; } if (def->isTagValue()) { // at "tag value", need to pop one more. if (pcomps.size() == 0) { // invalid path return; } pcomps.pop(); } } process_ref(rcomps, pcomps, ERROR_TYPE); } else if (cr_comp == "@@") { if (!got_tmpl) { // invalid path return; } if (def->isTypeless()) { // no value for typeless node return; } if (def->isValue()) { // invalid ref return; } if (def->isTag()) { // tag node vector cnodes; _cstore->cfgPathGetChildNodes(pcomps, cnodes, _active); for (size_t i = 0; i < cnodes.size(); i++) { pcomps.push(cnodes[i]); process_ref(rcomps, pcomps, def->getType(1)); pcomps.pop(); } } else { // handle leaf node handle_leaf = true; } } else { // just text. go down 1 level. if (got_tmpl && def->isTagNode()) { // 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; } else if (pcomps.size() == _orig_path_comps.size()) { // at the tag value. use the at_string. pcomps.push(_at_string); } else { // within the original path. take the original tag value. pcomps.push(_orig_path_comps[pcomps.size()]); } } pcomps.push(cr_comp); process_ref(rcomps, pcomps, ERROR_TYPE); } if (handle_leaf) { if (def->isMulti()) { // multi-value node vector vals; if (!_cstore->cfgPathGetValues(pcomps, vals, _active)) { return; } string val; for (size_t i = 0; i < vals.size(); i++) { if (val.length() > 0) { val += " "; } val += vals[i]; } pcomps.push(val); // treat "joined" multi-values as TEXT_TYPE _paths.push_back(pair(pcomps, TEXT_TYPE)); // at leaf. stop recursion. } else { // single-value node string val; vtw_type_e t = def->getType(1); if (!_cstore->cfgPathGetValue(pcomps, val, _active)) { /* can't get value => treat it as non-existent (empty value * and type ERROR_TYPE) */ val = ""; t = ERROR_TYPE; } pcomps.push(val); _paths.push_back(pair(pcomps, t)); // at leaf. stop recursion. } } } bool Cstore::VarRef::getValue(string& value, vtw_type_e& def_type) { vector result; MapT added; def_type = ERROR_TYPE; for (size_t 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 => empty string added[""] = true; result.push_back(""); 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 (size_t i = 0; i < result.size(); i++) { if (i > 0) { value += " "; } value += result[i]; } return true; } bool Cstore::VarRef::getSetPath(Cpath& path_comps) { if (_paths.size() != 1) { // for set_var_ref operation, there can be only one path. return false; } path_comps = _paths[0].first; /* note that for "varref set" operation, the varref must refer to the * "value" of a single-value leaf node, e.g., * "$VAR(plaintext-password/@)". so pop the last comp to give the * correct path for "set". the caller is responsible for verifying * whether the path points to a single-value leaf node. */ path_comps.pop(); return true; }