// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*- // Copyright (c) 2001-2007 International Computer Science Institute // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software") // to deal in the Software without restriction, subject to the conditions // listed in the XORP LICENSE file. These conditions include: you must // preserve this copyright notice, and you cannot mention the copyright // holders in advertising related to the Software without their permission. // The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This // notice is a summary of the XORP LICENSE file; the license in that file is // legally binding. #ident "$XORP: xorp/rtrmgr/conf_tree.cc,v 1.52 2007/02/16 22:47:21 pavlin Exp $" #include "rtrmgr_module.h" #include "libxorp/xorp.h" #include "libxorp/xlog.h" #include "libxorp/debug.h" #include "libxorp/utils.hh" #include "conf_tree.hh" #include "template_commands.hh" #include "template_tree.hh" #include "template_tree_node.hh" #include "util.hh" extern int init_bootfile_parser(const char* configuration, const char* filename, ConfigTree* ct); extern void parse_bootfile() throw (ParseError); extern int booterror(const char* s) throw (ParseError); /************************************************************************* * Config File class *************************************************************************/ ConfigTree::ConfigTree(TemplateTree* tt, bool verbose) : _template_tree(tt), _verbose(verbose) { } ConfigTree::~ConfigTree() { // XXX: _root_node will handle the deletion of all the tree nodes } #if 0 ConfigTree& ConfigTree::operator=(const ConfigTree& orig_tree) { root_node().clone_subtree(orig_tree.const_root_node()); return *this; } #endif bool ConfigTree::parse(const string& configuration, const string& config_file, string& error_msg) { try { init_bootfile_parser(configuration.c_str(), config_file.c_str(), this); parse_bootfile(); return true; } catch (const ParseError& pe) { error_msg = pe.why(); } return false; } void ConfigTree::add_default_children() { root_node().recursive_add_default_children(); } const TemplateTreeNode* ConfigTree::find_template(const list& path_segments) const { const TemplateTreeNode *ttn; debug_msg("----------------------------------------------------------\n"); debug_msg("looking for template for \"%s\"\n", path_as_string(path_segments).c_str()); ttn = _template_tree->find_node(path_segments); return ttn; } const TemplateTreeNode* ConfigTree::find_template_by_type(const list& path_segments) const { const TemplateTreeNode *ttn; ttn = _template_tree->find_node_by_type(path_segments); return ttn; } list ConfigTree::path_as_segments() const { list path_segments; ConfigTreeNode* ctn = _current_node; while (ctn->parent() != NULL) { path_segments.push_front(ConfPathSegment(ctn->segname(), ctn->type(), ctn->node_id())); ctn = ctn->parent(); } return path_segments; } string ConfigTree::current_path_as_string() const { string path; ConfigTreeNode* ctn = _current_node; while (ctn->parent() != NULL) { if (ctn == _current_node) path = ctn->segname(); else path = ctn->segname() + " " + path; ctn = ctn->parent(); } return path; } string ConfigTree::path_as_string(const list& path_segments) const { string path; list::const_iterator iter; for (iter = path_segments.begin(); iter != path_segments.end(); ++iter) { if (path.empty()) path = *iter; else path += " " + *iter; } return path; } void ConfigTree::extend_path(const string& segment, int type, const ConfigNodeId& node_id) { debug_msg("extend_path: %s, %s\n", segment.c_str(), node_id.str().c_str()); _path_segments.push_back(ConfPathSegment(segment, type, node_id)); } void ConfigTree::pop_path() { size_t segments_to_pop = _segment_lengths.front(); _segment_lengths.pop_front(); for (size_t i = 0; i < segments_to_pop; i++) { _current_node = _current_node->parent(); } } void ConfigTree::push_path() { string path = current_path_as_string(); string nodename = _path_segments.back().segname(); // // Keep track of how many segments comprise this frame so we can // pop the right number later. // size_t len = _path_segments.size(); _segment_lengths.push_front(len); list::const_iterator iter; for (iter = _path_segments.begin(); iter != _path_segments.end(); ++iter) { add_node(iter->segname(), iter->type(), iter->node_id()); } _path_segments.clear(); } void ConfigTree::add_node(const string& segment, int type, const ConfigNodeId& node_id) throw (ParseError) { list::const_iterator iter; ConfigTreeNode *found = NULL; if (_current_node->template_tree_node() != NULL && _current_node->template_tree_node()->children().empty()) { // // The current node's template has no children, and we've been // asked to add a node as a child of it. Either this is an // error, or this current node is a terminal node, and this // segment is actually a value. // if (_current_node->is_leaf_value()) { terminal_value(segment, _current_node->type(), OP_ASSIGN); } else { booterror("Invalid child node"); } // // If we're still here, we didn't throw a parse error. One // minor glitch is that need to decrement the path segments // before poppping, because this has become infated due to // counting the final value. // size_t segments_to_pop = _segment_lengths.front(); _segment_lengths.pop_front(); segments_to_pop--; _segment_lengths.push_front(segments_to_pop); return; } iter = _current_node->children().begin(); while (iter != _current_node->children().end()) { if ((*iter)->segname() == segment) { if (found != NULL) { // // If there are two nodes with the same segment name, // we can only distinguish between them by type. // extend_path doesn't have the type information // available because it wasn't at the relevant point // in the template file, so this is an error. The // correct way to step past such a node would be // through a call to add_node(). // string err = "Need to qualify type of " + segment + "\n"; xorp_throw(ParseError, err); } found = *iter; } ++iter; } if (found != NULL) { _current_node = found; } else { list path_segments = path_as_segments(); path_segments.push_back(ConfPathSegment(segment, type, node_id)); const TemplateTreeNode* ttn = find_template_by_type(path_segments); if (ttn == NULL) { booterror("No template found in template map"); } string path = current_path_as_string(); if (path.empty()) path = segment; else { debug_msg("path: >%s<\n", path.c_str()); debug_msg("segment: >%s<\n", segment.c_str()); path += " " + segment; } found = create_node(segment, path, ttn, _current_node, node_id, /* user_id */ 0, _verbose); _current_node = found; } } void ConfigTree::terminal_value(const string& value, int type, ConfigOperator op) throw (ParseError) { string error_msg; string path(current_path_as_string()); string svalue = value; ConfigTreeNode *ctn = _current_node; XLOG_ASSERT(ctn != NULL); // Special case for bool types to avoid needing to type "true" if (svalue == "" && (type == NODE_VOID)) { if (ctn->type() == NODE_BOOL) { type = NODE_BOOL; svalue = "true"; } } if ((ctn->type() == NODE_TEXT) && (type == NODE_TEXT)) { svalue = unquote(svalue); } else if ((ctn->type() == NODE_TEXT) && (type != NODE_TEXT)) { // We'll accept anything as text } else if ((ctn->type() == NODE_UINTRANGE) && (type == NODE_UINT)) { // Expand a single uint to a uintrange svalue += ".." + value; } else if ((ctn->type() == NODE_IPV4RANGE) && (type == NODE_IPV4)) { // Expand a single IPv4 to a ipv4range svalue += ".." + value; } else if ((ctn->type() == NODE_IPV6RANGE) && (type == NODE_IPV6)) { // Expand a single IPv6 to a ipv6range svalue += ".." + value; // Special case for bool types to avoid needing to type "true" } else if ((ctn->type() != NODE_TEXT) && (type == NODE_TEXT)) { // // The value was quoted in the bootfile. We can't tell if // there's a mismatch without doing some secondary parsing. // svalue = unquote(svalue); switch (ctn->type()) { case NODE_VOID: // Not clear what to do here break; case NODE_UINT: for (size_t i = 0; i < svalue.size(); i++) { if ((svalue[i] < '0') || (svalue[i] > '9')) { goto parse_error; } } break; case NODE_BOOL: if (svalue == "true" || svalue == "false" || svalue == "") break; goto parse_error; case NODE_IPV4: try { IPv4(svalue.c_str()); } catch (InvalidString) { goto parse_error; } break; case NODE_IPV4NET: try { IPv4Net(svalue.c_str()); } catch (InvalidString) { goto parse_error; } break; case NODE_IPV6: try { IPv6(svalue.c_str()); } catch (InvalidString) { goto parse_error; } break; case NODE_IPV6NET: try { IPv6Net(svalue.c_str()); } catch (InvalidString) { goto parse_error; } break; case NODE_MACADDR: try { Mac(svalue.c_str()); } catch (InvalidString) { goto parse_error; } break; case NODE_URL_FILE: case NODE_URL_FTP: case NODE_URL_HTTP: case NODE_URL_TFTP: // TODO: we cannot do easily a secondary parsing of URLs break; default: // Did we forget to add a new type? XLOG_FATAL("Unexpected type %d received", ctn->type()); } } else if (ctn->type() != type) { error_msg = "\"" + path + "\" has type " + ctn->typestr() + ", and value " + svalue + " is not a valid " + ctn->typestr(); booterror(error_msg.c_str()); } if (ctn->is_read_only() && ctn->is_leaf_value() && (! ctn->is_default_value(svalue))) { error_msg = "\"" + path + "\" is read-only node"; booterror(error_msg.c_str()); } if (ctn->set_value(svalue, /* userid */ 0, error_msg) != true) { error_msg = c_format("Cannot set the value of \"%s\": %s", path.c_str(), error_msg.c_str()); booterror(error_msg.c_str()); } if (ctn->set_operator(op, /* userid */ 0, error_msg) != true) { error_msg = c_format("Cannot set the operator for \"%s\": %s", path.c_str(), error_msg.c_str()); booterror(error_msg.c_str()); } return; parse_error: error_msg = "\"" + path + "\" has type " + ctn->typestr() + ", and value " + svalue + " is not a valid " + ctn->typestr(); booterror(error_msg.c_str()); } const ConfigTreeNode* ConfigTree::find_config_node(const list& path_segments) const { const ConfigTreeNode *found = &const_root_node(); const ConfigTreeNode *found2 = found; list::const_iterator pi; list::const_iterator ci; for (pi = path_segments.begin(); pi != path_segments.end(); ++pi) { for (ci = found->const_children().begin(); ci != found->const_children().end(); ++ci) { if (*pi == (*ci)->segname()) { found2 = *ci; break; } } if (found2 == found) return NULL; found = found2; } return found; } string ConfigTree::show_subtree(bool show_top, const list& path_segments, bool numbered, bool suppress_default_values) const { const ConfigTreeNode *found = find_config_node(path_segments); if (found == NULL) return "ERROR"; string s = found->show_subtree(show_top, /* depth */ 0, /* indent */ 0, /* do_indent */ true, numbered, /* annotate */ true, suppress_default_values); return s; } string ConfigTree::show_tree(bool numbered) const { return const_root_node().show_subtree(/* show_top */ false, /* depth */ 0, /* indent */ 0, /* do_indent */ true, numbered, /* annotate */ true, /* suppress_default_values */ false); } string ConfigTree::show_unannotated_tree(bool numbered) const { return const_root_node().show_subtree(/* show_top */ false, /* depth */ 0, /* indent */ 0, /* do_indent */ true, numbered, /* annotate */ false, /* suppress_default_values */ false); } ConfigTreeNode* ConfigTree::find_node(const list& path) { return root_node().find_node(path); } const ConfigTreeNode* ConfigTree::find_const_node(const list& path) const { return const_root_node().find_const_node(path); } ConfigTreeNode* ConfigTree::find_config_module(const string& module_name) { return root_node().find_config_module(module_name); } string ConfigTree::tree_str() const { return const_root_node().subtree_str(); } bool ConfigTree::apply_deltas(uid_t user_id, const string& deltas, bool provisional_change, bool preserve_node_id, string& response) { XLOG_TRACE(_verbose, "CT apply_deltas %u %s\n", XORP_UINT_CAST(user_id), deltas.c_str()); ConfigTree* delta_tree = create_tree(_template_tree, _verbose); if (delta_tree->parse(deltas, "", response) == false) return false; debug_msg("Delta tree:\n"); debug_msg("%s", delta_tree->tree_str().c_str()); debug_msg("end delta tree.\n"); response = ""; bool result; result = root_node().merge_deltas(user_id, delta_tree->const_root_node(), provisional_change, preserve_node_id, response); delete delta_tree; return result; } bool ConfigTree::apply_deletions(uid_t user_id, const string& deletions, bool provisional_change, string& response) { XLOG_TRACE(_verbose, "CT apply_deletions %u %s\n", XORP_UINT_CAST(user_id), deletions.c_str()); ConfigTree *deletion_tree = create_tree(_template_tree, _verbose); if (deletion_tree->parse(deletions, "", response) == false) return false; debug_msg("Deletion tree:\n"); debug_msg("%s", deletion_tree->tree_str().c_str()); debug_msg("end deletion tree.\n"); response = ""; bool result; result = root_node().merge_deletions(user_id, deletion_tree->const_root_node(), provisional_change, response); delete deletion_tree; return result; } void ConfigTree::retain_different_nodes(const ConfigTree& them, bool retain_changed_values) { root_node().retain_different_nodes(them.const_root_node(), retain_changed_values); } void ConfigTree::retain_deletion_nodes(const ConfigTree& them, bool retain_changed_values) { root_node().retain_deletion_nodes(them.const_root_node(), retain_changed_values); } void ConfigTree::retain_common_nodes(const ConfigTree& them) { root_node().retain_common_nodes(them.const_root_node()); } void ConfigTree::expand_varname_to_matchlist(const string& varname, list& matches) const { // Trim $( and ) string trimmed = varname.substr(2, varname.size()-3); // Split on dots list sl = split(trimmed, '.'); // Copy into a vector size_t len = sl.size(); vector v(len); for (size_t i = 0; i < len; i++) { v[i] = sl.front(); sl.pop_front(); } const_root_node().expand_varname_to_matchlist(v, 0, matches); }