// -*- 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/master_conf_tree.cc,v 1.77 2007/03/15 07:39:53 pavlin Exp $"
#include "rtrmgr_module.h"
#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/utils.hh"
#ifdef HAVE_GRP_H
#include <grp.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#include "master_conf_tree.hh"
#include "module_command.hh"
#include "rtrmgr_error.hh"
#include "template_commands.hh"
#include "master_template_tree.hh"
#include "master_template_tree_node.hh"
#include "util.hh"
#ifdef HOST_OS_WINDOWS
// XXX: Use unlink emulation from MS VC runtime.
#ifdef unlink
#undef unlink
#endif
#define unlink(x) _unlink(x)
// Stub out umask.
#ifdef umask
#undef umask
#endif
#define umask(x)
// Stub out fchown.
#ifdef fchown
#undef fchown
#endif
#define fchown(x,y,z) (0)
#endif
//
// The strings that are used to add and delete a load or save file, to
// access the content of the loaded file, etc.
//
// XXX: sigh, hard-coding the strings here is bad...
static string RTRMGR_CONFIG_NODE = "rtrmgr";
static string RTRMGR_CONFIG = "rtrmgr {\n}\n";
static string RTRMGR_SAVE_FILE_CONFIG = "rtrmgr {\n save %s\n}\n";
static string RTRMGR_LOAD_FILE_CONFIG = "rtrmgr {\n load %s\n}\n";
static string RTRMGR_CONFIG_FILENAME_VARNAME = "$(rtrmgr.CONFIG_FILENAME)";
/*************************************************************************
* Master Config Tree class
*************************************************************************/
MasterConfigTree::MasterConfigTree(const string& config_file,
MasterTemplateTree* tt,
ModuleManager& mmgr,
XorpClient& xclient,
bool global_do_exec,
bool verbose) throw (InitError)
: ConfigTree(tt, verbose),
_root_node(verbose),
_commit_in_progress(false),
_config_failed(false),
_rtrmgr_config_node_found(false),
_xorp_gid(0),
_is_xorp_gid_set(false),
//
// XXX: set _enable_program_exec_id to true to enable
// setting the user and group ID when executing external helper
// programs during (re)configuration. Note that this doesn't apply
// for running the XORP processes themselves.
//
_enable_program_exec_id(false),
_config_tree_copy(NULL)
{
string configuration;
string error_msg;
_current_node = &_root_node;
_task_manager = new TaskManager(*this, mmgr, xclient,
global_do_exec, verbose);
#ifdef HAVE_GRP_H
// Get the group ID for group "xorp"
struct group* grp = getgrnam("xorp");
if (grp != NULL) {
_xorp_gid = grp->gr_gid;
_is_xorp_gid_set = true;
}
#else
_xorp_gid = 0;
_is_xorp_gid_set = true;
#endif
if (read_file(configuration, config_file, error_msg) != true) {
xorp_throw(InitError, error_msg);
}
if (parse(configuration, config_file, error_msg) != true) {
xorp_throw(InitError, error_msg);
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
add_default_children();
if (root_node().check_config_tree(error_msg) != true) {
xorp_throw(InitError, error_msg);
}
//
// If we got this far, it looks like we have a good bootfile.
// The bootfile is now stored in the ConfigTree.
// Now do the second pass, starting processes and configuring things.
//
execute();
}
MasterConfigTree::MasterConfigTree(TemplateTree* tt, bool verbose)
: ConfigTree(tt, verbose),
_root_node(verbose),
_task_manager(NULL),
_config_tree_copy(NULL)
{
_current_node = &_root_node;
}
MasterConfigTree::~MasterConfigTree()
{
remove_tmp_config_file();
delete _task_manager;
if (_config_tree_copy != NULL)
delete _config_tree_copy;
}
MasterConfigTree&
MasterConfigTree::operator=(const MasterConfigTree& orig_tree)
{
master_root_node().clone_subtree(orig_tree.const_master_root_node());
return *this;
}
ConfigTree*
MasterConfigTree::create_tree(TemplateTree *tt, bool verbose)
{
MasterConfigTree *mct;
mct = new MasterConfigTree(tt, verbose);
return mct;
}
bool
MasterConfigTree::read_file(string& configuration,
const string& config_file,
string& error_msg)
{
FILE* file = fopen(config_file.c_str(), "r");
if (file == NULL) {
error_msg = c_format("Failed to open config file: %s\n",
config_file.c_str());
return false;
}
static const uint32_t MCT_READBUF = 8192;
char buf[MCT_READBUF + 1];
while (feof(file) == 0) {
size_t bytes = fread(buf, 1, sizeof(buf) - 1, file);
// NUL terminate it.
buf[bytes] = '\0';
if (bytes > 0) {
configuration += buf;
}
}
fclose(file);
return true;
}
bool
MasterConfigTree::parse(const string& configuration,
const string& config_file,
string& error_msg)
{
if (ConfigTree::parse(configuration, config_file, error_msg) != true)
return false;
string s = show_tree(/*numbered*/ true);
debug_msg("== MasterConfigTree::parse yields ==\n%s\n"
"====================================\n", s.c_str());
return true;
}
void
MasterConfigTree::execute()
{
debug_msg("##############################################################\n");
debug_msg("MasterConfigTree::execute\n");
list<string> changed_modules = find_changed_modules();
list<string>::const_iterator iter;
string s;
for (iter = changed_modules.begin();
iter != changed_modules.end();
++iter) {
if (! s.empty())
s += ", ";
s += (*iter);
}
XLOG_INFO("Changed modules: %s", s.c_str());
_commit_cb = callback(this, &MasterConfigTree::config_done);
commit_changes_pass2();
}
void
MasterConfigTree::config_done(bool success, string error_msg)
{
if (success)
debug_msg("Configuration done: success\n");
else
XLOG_ERROR("Configuration failed: %s", error_msg.c_str());
_config_failed = !success;
if (! success) {
_config_failed = true;
_config_failed_msg = error_msg;
return;
}
string error_msg2;
if (check_commit_status(error_msg2) == false) {
XLOG_ERROR("%s", error_msg2.c_str());
_config_failed = true;
_config_failed_msg = error_msg2;
return;
}
debug_msg("MasterConfigTree::config_done returning\n");
}
ConfigTreeNode*
MasterConfigTree::create_node(const string& segment, const string& path,
const TemplateTreeNode* ttn,
ConfigTreeNode* parent_node,
const ConfigNodeId& node_id,
uid_t user_id, bool verbose)
{
MasterConfigTreeNode *ctn, *parent;
parent = dynamic_cast<MasterConfigTreeNode *>(parent_node);
if (parent_node != NULL)
XLOG_ASSERT(parent != NULL);
ctn = new MasterConfigTreeNode(segment, path, ttn, parent, node_id,
user_id, verbose);
return reinterpret_cast<ConfigTreeNode*>(ctn);
}
list<string>
MasterConfigTree::find_changed_modules() const
{
debug_msg("Find changed modules\n");
set<string> changed_modules;
const_master_root_node().find_changed_modules(changed_modules);
list<string> ordered_modules;
order_module_list(changed_modules, ordered_modules);
return ordered_modules;
}
list<string>
MasterConfigTree::find_active_modules() const
{
debug_msg("Find active modules\n");
set<string> active_modules;
const_master_root_node().find_active_modules(active_modules);
list<string> ordered_modules;
order_module_list(active_modules, ordered_modules);
return ordered_modules;
}
list<string>
MasterConfigTree::find_inactive_modules() const
{
debug_msg("Find inactive modules\n");
set<string> all_modules;
list<string> ordered_all_modules;
debug_msg("All modules:\n");
const_master_root_node().find_all_modules(all_modules);
order_module_list(all_modules, ordered_all_modules);
set<string> active_modules;
list<string> ordered_active_modules;
debug_msg("Active modules:\n");
const_master_root_node().find_active_modules(active_modules);
order_module_list(active_modules, ordered_active_modules);
// Remove things that are common to both lists
while (!ordered_active_modules.empty()) {
list<string>::iterator iter;
bool found = false;
for (iter = ordered_all_modules.begin();
iter != ordered_all_modules.end();
++iter) {
if (*iter == ordered_active_modules.front()) {
ordered_all_modules.erase(iter);
ordered_active_modules.pop_front();
found = true;
break;
}
}
XLOG_ASSERT(found == true);
}
debug_msg("Inactive Module Order: ");
list<string>::const_iterator final_iter;
for (final_iter = ordered_all_modules.begin();
final_iter != ordered_all_modules.end();
++final_iter) {
debug_msg("%s ", final_iter->c_str());
}
debug_msg("\n");
return ordered_all_modules;
}
void
MasterConfigTree::order_module_list(const set<string>& module_set,
list<string>& ordered_modules) const
{
multimap<string, string> depends; // first depends on second
set<string> no_info; // modules we found no info about
set<string> satisfied; // modules we found no info about
set<string> additional_modules; // modules that we depend on that
// aren't in module_set
//
// We've found the list of modules that have changed that need to
// be applied. Now we need to sort them so that the modules to be
// performed first are at the beginning of the list.
//
// Handle the degenerate cases simply
if (module_set.empty())
return;
set<string>::const_iterator iter;
for (iter = module_set.begin(); iter != module_set.end(); ++iter) {
ModuleCommand* mc = _template_tree->find_module(*iter);
if (mc == NULL) {
no_info.insert(*iter);
debug_msg("%s has no module info\n", (*iter).c_str());
continue;
}
if (mc->depends().empty()) {
//
// This module doesn't depend on anything else, so it can
// be started early in the sequence.
//
ordered_modules.push_back(*iter);
satisfied.insert(*iter);
debug_msg("%s has no dependencies\n", (*iter).c_str());
continue;
}
list<string>::const_iterator di;
for (di = mc->depends().begin(); di != mc->depends().end(); ++di) {
debug_msg("%s depends on %s\n", iter->c_str(), di->c_str());
depends.insert(pair<string,string>(*iter, *di));
// Check that the dependency is already in our list of modules.
if (module_set.find(*di)==module_set.end()) {
// If not, add it.
additional_modules.insert(*di);
}
}
}
debug_msg("doing additional modules\n");
// Figure out the dependencies for all the additional modules
set<string> additional_done;
while (!additional_modules.empty()) {
set<string>::iterator mod_iter;
mod_iter = additional_modules.begin();
ModuleCommand* mc = _template_tree->find_module(*mod_iter);
if (mc == NULL) {
debug_msg("%s has no info\n", (*mod_iter).c_str());
additional_done.insert(*mod_iter);
additional_modules.erase(mod_iter);
ordered_modules.push_back(*mod_iter);
satisfied.insert(*mod_iter);
continue;
}
if (mc->depends().empty()) {
debug_msg("%s has no dependencies\n", (*mod_iter).c_str());
additional_done.insert(*mod_iter);
ordered_modules.push_back(*mod_iter);
satisfied.insert(*mod_iter);
additional_modules.erase(mod_iter);
continue;
}
list<string>::const_iterator di;
for (di = mc->depends().begin(); di != mc->depends().end(); ++di) {
depends.insert(pair<string,string>(*mod_iter, *di));
debug_msg("%s depends on %s\n", mod_iter->c_str(), di->c_str());
// Check that the dependency is already in our list of modules.
if (module_set.find(*di) == module_set.end()
&& additional_modules.find(*di) == additional_modules.end()
&& additional_done.find(*di) == additional_done.end()) {
// If not, add it.
additional_modules.insert(*di);
}
}
additional_done.insert(*mod_iter);
additional_modules.erase(mod_iter);
}
debug_msg("done additional modules\n");
multimap<string,string>::iterator curr_iter, next_iter;
while (!depends.empty()) {
bool progress_made = false;
curr_iter = depends.begin();
while (curr_iter != depends.end()) {
next_iter = curr_iter;
++next_iter;
debug_msg("searching for dependency for %s on %s\n",
curr_iter->first.c_str(), curr_iter->second.c_str());
if (satisfied.find(curr_iter->second) != satisfied.end()) {
// Rule is now satisfied.
string module = curr_iter->first;
depends.erase(curr_iter);
progress_made = true;
debug_msg("dependency of %s on %s satisfied\n",
module.c_str(), curr_iter->second.c_str());
if (depends.find(module) == depends.end()) {
// This was the last dependency
satisfied.insert(module);
ordered_modules.push_back(module);
debug_msg("dependencies for %s now satisfied\n",
module.c_str());
}
}
curr_iter = next_iter;
}
if (progress_made == false) {
XLOG_FATAL("Module dependencies cannot be satisfied");
}
}
// finally add the modules for which we have no information
for (iter = no_info.begin(); iter != no_info.end(); ++iter) {
ordered_modules.push_back(*iter);
}
debug_msg("Module Order: ");
list<string>::const_iterator final_iter;
for (final_iter = ordered_modules.begin();
final_iter != ordered_modules.end();
++final_iter) {
debug_msg("%s ", final_iter->c_str());
}
debug_msg("\n");
}
void
MasterConfigTree::commit_changes_pass1(CallBack cb)
{
string error_msg;
debug_msg("##############################################################\n");
debug_msg("MasterConfigTree::commit_changes_pass1\n");
_commit_in_progress = true;
list<string> changed_modules = find_changed_modules();
list<string> inactive_modules = find_inactive_modules();
list<string>::const_iterator iter;
debug_msg("Changed modules:\n");
for (iter = changed_modules.begin();
iter != changed_modules.end();
++iter) {
debug_msg("%s ", (*iter).c_str());
}
debug_msg("\n");
//
// Two passes: the first checks for errors. If no errors are
// found, attempt the actual commit.
//
/*******************************************************************/
/* Pass 1: check for errors without actually doing anything */
/*******************************************************************/
_task_manager->reset();
_task_manager->set_do_exec(false, true);
_task_manager->set_exec_id(_exec_id);
_commit_cb = cb;
master_root_node().initialize_commit();
if (root_node().check_config_tree(error_msg) == false) {
// Something went wrong - return the error message.
_commit_in_progress = false;
cb->dispatch(false, error_msg);
return;
}
// Sort the changes in order of module dependencies
for (iter = changed_modules.begin();
iter != changed_modules.end();
++iter) {
if (!module_config_start(*iter, error_msg)) {
_commit_in_progress = false;
cb->dispatch(false, error_msg);
return;
}
}
bool needs_activate = false;
bool needs_update = false;
if (master_root_node().commit_changes(*_task_manager,
/* do_commit = */ false,
0, 0,
error_msg,
needs_activate,
needs_update)
== false) {
// Something went wrong - return the error message.
_commit_in_progress = false;
cb->dispatch(false, error_msg);
return;
}
#if 0
//
// XXX: don't shutdown any modules yet, because in this stage
// we are checking only for errors.
//
for (iter = inactive_modules.begin();
iter != inactive_modules.end(); ++iter) {
_task_manager->shutdown_module(*iter);
}
#endif // 0
_task_manager->run(callback(this, &MasterConfigTree::commit_pass1_done));
}
void
MasterConfigTree::commit_pass1_done(bool success, string error_msg)
{
debug_msg("##############################################################\n");
debug_msg("## commit_pass1_done\n");
if (success) {
commit_changes_pass2();
} else {
string msg = "Commit pass 1 failed: " + error_msg;
XLOG_ERROR("%s", msg.c_str());
_commit_in_progress = false;
_commit_cb->dispatch(false, error_msg);
}
}
void
MasterConfigTree::commit_changes_pass2()
{
string error_msg;
debug_msg("##############################################################\n");
debug_msg("## commit_changes_pass2\n");
_commit_in_progress = true;
if (root_node().check_config_tree(error_msg) == false) {
XLOG_ERROR("Configuration tree error: %s", error_msg.c_str());
_commit_in_progress = false;
_commit_cb->dispatch(false, error_msg);
return;
}
/*******************************************************************/
/* Pass 2: implement the changes */
/*******************************************************************/
list<string> changed_modules = find_changed_modules();
list<string> inactive_modules = find_inactive_modules();
list<string>::const_iterator iter;
_task_manager->reset();
_task_manager->set_do_exec(true, false);
_task_manager->set_exec_id(_exec_id);
master_root_node().initialize_commit();
// Sort the changes in order of module dependencies
for (iter = changed_modules.begin();
iter != changed_modules.end();
++iter) {
if (!module_config_start(*iter, error_msg)) {
XLOG_ERROR("Commit failed in deciding startups");
_commit_in_progress = false;
_commit_cb->dispatch(false, error_msg);
return;
}
}
bool needs_activate = false;
bool needs_update = false;
if (!master_root_node().commit_changes(*_task_manager,
/* do_commit = */ true,
0, 0,
error_msg,
needs_activate,
needs_update)) {
// Abort the commit
XLOG_ERROR("Commit failed in config tree");
_commit_in_progress = false;
_commit_cb->dispatch(false, error_msg);
return;
}
for (iter = inactive_modules.begin();
iter != inactive_modules.end();
++iter) {
_task_manager->shutdown_module(*iter);
}
_task_manager->run(callback(this, &MasterConfigTree::commit_pass2_done));
}
void
MasterConfigTree::commit_pass2_done(bool success, string error_msg)
{
debug_msg("##############################################################\n");
debug_msg("## commit_pass2_done\n");
if (success)
debug_msg("## commit seems successful\n");
else
XLOG_ERROR("Commit failed: %s", error_msg.c_str());
_commit_cb->dispatch(success, error_msg);
_commit_in_progress = false;
}
bool
MasterConfigTree::check_commit_status(string& error_msg)
{
debug_msg("check_commit_status\n");
bool success = master_root_node().check_commit_status(error_msg);
if (success) {
// If the commit was successful, clear all the temporary state.
debug_msg("commit was successful, finalizing...\n");
master_root_node().finalize_commit();
debug_msg("finalizing done\n");
}
return success;
}
string
MasterConfigTree::discard_changes()
{
debug_msg("##############################################################\n");
debug_msg("MasterConfigTree::discard_changes\n");
string result = root_node().discard_changes(0, 0);
debug_msg("##############################################################\n");
return result;
}
string
MasterConfigTree::mark_subtree_for_deletion(const list<string>& path_segments,
uid_t user_id)
{
ConfigTreeNode *found = find_node(path_segments);
if (found == NULL)
return "ERROR";
if ((found->parent() != NULL)
&& found->parent()->is_tag()
&& found->parent()->children().size()==1) {
found = found->parent();
}
found->mark_subtree_for_deletion(user_id);
return string("OK");
}
void
MasterConfigTree::delete_entire_config()
{
root_node().mark_subtree_for_deletion(0);
root_node().undelete();
// We don't need a verification pass - this isn't allowed to fail.
_commit_cb = callback(this, &MasterConfigTree::config_done);
commit_changes_pass2();
}
bool
MasterConfigTree::lock_node(const string& /* node */, uid_t /* user_id */,
uint32_t /* timeout */,
uint32_t& /* holder */)
{
// TODO: XXX: not implemented yet
return true;
}
bool
MasterConfigTree::unlock_node(const string& /* node */, uid_t /* user_id */)
{
// TODO: XXX: not implemented yet
return true;
}
bool
MasterConfigTree::save_to_file(const string& filename, uid_t user_id,
string& error_msg)
{
string dummy_error_msg;
string full_filename = config_full_filename(filename);
FILE* file = NULL;
error_msg = "";
//
// TODO: there are lots of hard-coded values below. Fix this!
//
//
// Set the effective group to "xorp" and the effective user ID to the
// uid of the user that sent the request.
//
if (! _is_xorp_gid_set) {
error_msg = "Group \"xorp\" does not exist on this system";
return false;
}
_exec_id.set_uid(user_id);
_exec_id.set_gid(_xorp_gid);
_exec_id.save_current_exec_id();
if (_exec_id.set_effective_exec_id(error_msg) != XORP_OK) {
_exec_id.restore_saved_exec_id(dummy_error_msg);
return false;
}
#ifdef HOST_OS_WINDOWS
{
#else
// Set a umask of 664, to allow sharing of config files between
// users in group "xorp".
mode_t orig_mask = umask(S_IWOTH);
struct stat sb;
if (stat(full_filename.c_str(), &sb) == 0) {
if ((sb.st_mode & S_IFREG) == 0) {
if (((sb.st_mode & S_IFMT) == S_IFCHR) ||
((sb.st_mode & S_IFMT) == S_IFBLK)) {
error_msg = c_format("File %s is a special device.\n",
full_filename.c_str());
} else if ((sb.st_mode & S_IFMT) == S_IFIFO) {
error_msg = c_format("File %s is a named pipe.\n",
full_filename.c_str());
} else if ((sb.st_mode & S_IFMT) == S_IFDIR) {
error_msg = c_format("File %s is a directory.\n",
full_filename.c_str());
}
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
#endif // ! HOST_OS_WINDOWS
file = fopen(full_filename.c_str(), "r");
if (file != NULL) {
// We've been asked to overwrite a file
char line[80];
if (fgets(line, sizeof(line) - 1, file) == NULL) {
error_msg = c_format("File %s exists, but an error occured "
"when trying to check that it was OK to "
"overwrite it\n",
full_filename.c_str());
error_msg += "File was NOT overwritten\n";
fclose(file);
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
if (strncmp(line, "/*XORP", 6) != 0) {
error_msg = c_format("File %s exists, but it is not an "
"existing XORP config file.\n",
full_filename.c_str());
error_msg += "File was NOT overwritten\n";
fclose(file);
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
fclose(file);
file = NULL;
}
// It seems OK to overwrite this file
if (unlink(full_filename.c_str()) < 0 && errno != ENOENT) {
error_msg = c_format("File %s exists, and can not be "
"overwritten.\n",
full_filename.c_str());
error_msg += strerror(errno);
if (file != NULL) {
fclose(file);
}
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
}
file = fopen(full_filename.c_str(), "w");
if (file == NULL) {
error_msg = c_format("Could not create file \"%s\"",
full_filename.c_str());
error_msg += strerror(errno);
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
// Write the file header
string header = "/*XORP Configuration File, v1.0*/\n";
size_t bytes;
bytes = fwrite(header.c_str(), sizeof(char), header.size(), file);
if (bytes < header.size()) {
fclose(file);
error_msg = c_format("Error writing to file \"%s\"\n",
full_filename.c_str());
// We couldn't even write the header - clean up if we can
if (unlink(full_filename.c_str()) == 0) {
error_msg += "Save aborted; truncated file has been removed\n";
} else {
error_msg += "Save aborted; truncated file may exist\n";
}
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
// Write the config to the file
string config = show_unannotated_tree(/*numbered*/ false);
bytes = fwrite(config.c_str(), sizeof(char), config.size(), file);
if (bytes < config.size()) {
fclose(file);
error_msg = c_format("Error writing to file \"%s\"\n",
full_filename.c_str());
error_msg += strerror(errno);
error_msg += "\n";
//
// We couldn't write the config - clean up if we can
//
if (unlink(full_filename.c_str()) == 0) {
error_msg += "Save aborted; truncated file has been removed\n";
} else {
error_msg += "Save aborted; truncated file may exist\n";
}
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return false;
}
// Set file group correctly
if (fchown(fileno(file), user_id, _xorp_gid) < 0) {
// This shouldn't be able to happen, but if it does, it
// shouldn't be fatal.
error_msg = "WARNING: failed to set saved file to be group \"xorp\"\n";
}
// Close properly and clean up
if (fclose(file) != 0) {
error_msg = c_format("Error closing file \"%s\"\n",
full_filename.c_str());
error_msg += strerror(errno) + string("\n");
error_msg += "File may not have been written correctly\n";
_exec_id.restore_saved_exec_id(dummy_error_msg);
return false;
}
error_msg += "Save complete\n";
_exec_id.restore_saved_exec_id(dummy_error_msg);
umask(orig_mask);
return true;
}
string
MasterConfigTree::config_full_filename(const string& filename) const
{
string result;
//
// If the name of the config directory wasn't set return the filename
// itself.
//
if (_config_directory.empty())
return (filename);
XLOG_ASSERT(! filename.empty());
//
// If this is an absolute filename then return the filename itself
//
if (is_absolute_path(filename))
return (filename);
//
// Concatenate the name of the config directory with the filename
//
result = _config_directory;
if (result[result.size() - 1] != PATH_DELIMITER_CHAR)
result += PATH_DELIMITER_STRING;
result += filename;
return (result);
}
void
MasterConfigTree::remove_tmp_config_file()
{
string tmp_config_filename_value;
if (root_node().expand_variable(RTRMGR_CONFIG_FILENAME_VARNAME,
tmp_config_filename_value,
false)
!= true) {
tmp_config_filename_value = "";
} else {
// Reset the variable
root_node().set_variable(RTRMGR_CONFIG_FILENAME_VARNAME, "");
}
//
// Unlink the file(s). Typically, the _tmp_config_filename value
// should be same as the expand_variable(RTRMGR_CONFIG_FILENAME_VARNAME)
// value. However, some of the template actions may explicitly set
// the value of that variable to something else, hence we need to cover
// that case.
//
if (! _tmp_config_filename.empty())
unlink(_tmp_config_filename.c_str());
if ((! tmp_config_filename_value.empty())
&& (tmp_config_filename_value != _tmp_config_filename)) {
unlink(tmp_config_filename_value.c_str());
}
_tmp_config_filename = "";
}
bool
MasterConfigTree::set_config_file_permissions(FILE* fp, uid_t user_id,
string& error_msg)
{
#ifdef HOST_OS_WINDOWS
UNUSED(fp);
UNUSED(user_id);
UNUSED(error_msg);
return true;
#else // ! HOST_OS_WINDOWS
//
// Set the user and group owner of the file, and change its permissions
// so it is group-writable.
//
struct group *grp = getgrnam("xorp");
if (grp == NULL) {
error_msg = c_format("group \"xorp\" does not exist on this system");
return false;
}
gid_t group_id = grp->gr_gid;
if (fchown(fileno(fp), user_id, group_id) != 0) {
error_msg = c_format("error changing the owner and group of the "
"file: %s",
strerror(errno));
return false;
}
if (fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
!= 0) {
error_msg = c_format("error changing the mode of the file: %s",
strerror(errno));
return false;
}
return true;
#endif // ! HOST_OS_WINDOWS
}
bool
MasterConfigTree::change_config(uid_t user_id, CallBack cb, string& error_msg)
{
string dummy_error_msg;
// Initialize the execution ID
if (_enable_program_exec_id) {
_exec_id.set_uid(user_id);
if (_is_xorp_gid_set)
_exec_id.set_gid(_xorp_gid);
}
commit_changes_pass1(cb);
if (config_failed()) {
error_msg = config_failed_msg();
return false;
}
return true;
}
bool
MasterConfigTree::apply_config_change(uid_t user_id, string& error_msg,
const string& deltas,
const string& deletions,
ConfigChangeCallBack cb)
{
//
// Create a copy of the local tree that will be used later to compute
// the configuration changes.
//
if (_config_tree_copy != NULL)
delete _config_tree_copy;
_config_tree_copy = new MasterConfigTree(_template_tree, verbose());
*_config_tree_copy = *this;
//
// Apply the configuration changes
//
if (apply_deltas(user_id, deltas,
true /* provisional_change */,
true /* preserve_node_id */,
error_msg)
== false) {
return (false);
}
if (apply_deletions(user_id, deletions, /* provisional_change */ true,
error_msg)
== false) {
return (false);
}
//
// Add nodes providing default values. Note: they shouldn't be
// needed, but adding them here acts as a safety mechanism against
// a client that forgets to add them.
//
add_default_children();
CallBack cb2 = callback(this,
&MasterConfigTree::apply_config_commit_changes_cb,
cb);
return (change_config(user_id, cb2, error_msg));
}
void
MasterConfigTree::apply_config_commit_changes_cb(bool success,
string error_msg,
ConfigChangeCallBack cb)
{
string deltas, deletions;
MasterConfigTree delta_tree(_template_tree, _verbose);
MasterConfigTree deletion_tree(_template_tree, _verbose);
//
// Compute the configuration changes
//
XLOG_ASSERT(_config_tree_copy != NULL);
_config_tree_copy->diff_configs(*this, delta_tree, deletion_tree);
deltas = delta_tree.show_unannotated_tree(/*numbered*/ true);
deletions = deletion_tree.show_unannotated_tree(/*numbered*/ true);
cb->dispatch(success, error_msg, deltas, deletions);
}
bool
MasterConfigTree::save_config(const string& filename, uid_t user_id,
string& error_msg, ConfigSaveCallBack cb)
{
string save_file_config;
XLOG_TRACE(_verbose, "Saving to file %s", filename.c_str());
//
// Create the string that is used to temporary add a save file to the
// configuration.
//
save_file_config = c_format(RTRMGR_SAVE_FILE_CONFIG.c_str(),
filename.c_str());
//
// Test whether the file name matches an entry in the template tree.
// If yes, then add a save file to the configuration so the appropriate
// external program will be invoked.
// Otherwise, assume that the user tries to save to a local file.
//
do {
MasterConfigTree new_tree(_template_tree, _verbose);
if (new_tree.parse(save_file_config, "", error_msg) == true) {
//
// The file name is recognizable by the parser, hence
// invoke the corresponding external methods for processing.
//
break;
}
//
// Assume that the user tries to save to a local file
//
if (save_to_file(filename, user_id, error_msg) != true) {
XLOG_TRACE(_verbose, "Failed to save file %s: %s",
filename.c_str(), error_msg.c_str());
return false;
}
XLOG_TRACE(_verbose, "Saved file %s", filename.c_str());
//
// Schedule a timer to dispatch immediately the callback
//
EventLoop& eventloop = xorp_client().eventloop();
_save_config_completed_timer = eventloop.new_oneoff_after(
TimeVal::ZERO(),
callback(this,
&MasterConfigTree::save_config_done_cb,
true, // success
string(""), // error_msg
cb));
return true;
} while (false);
//
// Create a temporary file that would be used by the external
// program to copy the configuration from.
//
do {
FILE* fp;
// Create the file
fp = xorp_make_temporary_file("", "xorp_rtrmgr_tmp_config_file",
_tmp_config_filename, error_msg);
if (fp == NULL) {
error_msg = c_format("Cannot save the configuration file: "
"cannot create a temporary filename: %s",
error_msg.c_str());
_tmp_config_filename = "";
return false;
}
// Set the file permissions
if (set_config_file_permissions(fp, user_id, error_msg) != true) {
error_msg = c_format("Cannot save the configuration file: %s",
error_msg.c_str());
// Close and remove the file
fclose(fp);
remove_tmp_config_file();
return false;
}
//
// Save the current configuration
//
string config = show_unannotated_tree(/*numbered*/ false);
if (fwrite(config.c_str(), sizeof(char), config.size(), fp)
!= static_cast<size_t>(config.size())) {
error_msg = c_format("Cannot save the configuration file: "
"error writing to a temporary file: %s",
strerror(errno));
// Close and remove the file
fclose(fp);
remove_tmp_config_file();
return false;
}
//
// Close the file descriptor, because we don't need it.
// Note, that the created file remains on the filesystem,
// so it is guaranteed to be unique when we need to use it again.
//
fclose(fp);
break;
} while (false);
//
// Check whether the current configuration already contains
// rtrmgr configuration.
//
list<string> path;
path.push_back(RTRMGR_CONFIG_NODE);
if (find_node(path) != NULL) {
_rtrmgr_config_node_found = true;
} else {
_rtrmgr_config_node_found = false;
}
//
// Add the temporary save file to the configuration
//
if (apply_deltas(user_id, save_file_config,
true /* provisional_change */,
false /* preserve_node_id */,
error_msg)
!= true) {
error_msg = c_format("Cannot save the configuration file: %s",
error_msg.c_str());
remove_tmp_config_file();
discard_changes();
return false;
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
add_default_children();
// Save the name of the temporary file in the local variable
if (root_node().set_variable(RTRMGR_CONFIG_FILENAME_VARNAME,
_tmp_config_filename.c_str())
!= true) {
error_msg = c_format("Cannot save the configuration file: "
"cannot store temporary filename %s in internal "
"variable %s",
_tmp_config_filename.c_str(),
RTRMGR_CONFIG_FILENAME_VARNAME.c_str());
remove_tmp_config_file();
return false;
}
//
// Specify the callback that will be invoked after we have saved the new
// configuration file so we can continue from there.
//
CallBack save_cb;
save_cb = callback(this, &MasterConfigTree::save_config_file_sent_cb,
filename, user_id, cb);
if (change_config(user_id, save_cb, error_msg) != true) {
remove_tmp_config_file();
discard_changes();
return false;
}
return true;
}
void
MasterConfigTree::save_config_file_sent_cb(bool success,
string error_msg,
string filename,
uid_t user_id,
ConfigSaveCallBack cb)
{
string dummy_error_msg;
//
// Remove the temporary file with the configuration
//
remove_tmp_config_file();
if (! success) {
XLOG_TRACE(_verbose, "Failed to save file %s: %s",
filename.c_str(), error_msg.c_str());
discard_changes();
cb->dispatch(success, error_msg);
return;
}
//
// Check everything really worked, and finalize the commit when
// the file was saved.
//
if (check_commit_status(error_msg) == false) {
XLOG_TRACE(_verbose, "Check commit status indicates failure: %s",
error_msg.c_str());
discard_changes();
cb->dispatch(false, error_msg);
return;
}
XLOG_TRACE(_verbose, "Saved file %s", filename.c_str());
//
// Create the string that is used to delete the temporary added
// save file from the configuration.
//
string delete_save_file_config;
if (_rtrmgr_config_node_found) {
delete_save_file_config = c_format(RTRMGR_SAVE_FILE_CONFIG.c_str(),
filename.c_str());
} else {
delete_save_file_config = RTRMGR_CONFIG;
}
if (apply_deletions(user_id, delete_save_file_config,
/* provisional_change */ true, error_msg)
!= true) {
error_msg = c_format("Cannot save the configuration file because of "
"internal error: %s", error_msg.c_str());
discard_changes();
cb->dispatch(false, error_msg);
return;
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
add_default_children();
//
// Specify the callback that will be invoked after we have cleaned-up
// so we can continue from there.
//
bool orig_success = success;
string orig_error_msg = error_msg;
CallBack cleanup_cb;
cleanup_cb = callback(this, &MasterConfigTree::save_config_file_cleanup_cb,
orig_success, orig_error_msg, filename, user_id, cb);
if (change_config(user_id, cleanup_cb, error_msg) != true) {
discard_changes();
cb->dispatch(false, error_msg);
return;
}
}
void
MasterConfigTree::save_config_file_cleanup_cb(bool success,
string error_msg,
bool orig_success,
string orig_error_msg,
string filename,
uid_t user_id,
ConfigSaveCallBack cb)
{
if (! orig_success) {
XLOG_TRACE(_verbose, "Failed to save file %s: %s",
filename.c_str(), orig_error_msg.c_str());
discard_changes();
cb->dispatch(orig_success, orig_error_msg);
return;
}
if (! success) {
XLOG_TRACE(_verbose, "Failed to save file %s: %s",
filename.c_str(), error_msg.c_str());
discard_changes();
cb->dispatch(success, error_msg);
return;
}
//
// Check everything really worked, and finalize the commit when
// we cleaned up the file that was temporarily added.
//
if (check_commit_status(error_msg) == false) {
XLOG_TRACE(_verbose, "Check commit status indicates failure: %s",
error_msg.c_str());
discard_changes();
cb->dispatch(false, error_msg);
return;
}
XLOG_TRACE(_verbose, "Cleanup completed after saving file %s",
filename.c_str());
save_config_done_cb(success, error_msg, cb);
UNUSED(user_id);
}
void
MasterConfigTree::save_config_done_cb(bool success, string error_msg,
ConfigSaveCallBack cb)
{
cb->dispatch(success, error_msg);
}
bool
MasterConfigTree::load_config(const string& filename, uid_t user_id,
string& error_msg, ConfigLoadCallBack cb)
{
XLOG_TRACE(_verbose, "Loading file %s", filename.c_str());
//
// Create the string that is used to temporary add a load file to the
// configuration.
//
string load_file_config = c_format(RTRMGR_LOAD_FILE_CONFIG.c_str(),
filename.c_str());
//
// Test whether the file name matches an entry in the template tree.
// If yes, then add a load file to the configuration so the appropriate
// external program will be invoked.
// Otherwise, assume that the user tries to load from a local file.
//
do {
MasterConfigTree new_tree(_template_tree, _verbose);
if (new_tree.parse(load_file_config, "", error_msg) == true) {
//
// The file name is recognizable by the parser, hence
// invoke the corresponding external methods for processing.
//
break;
}
//
// Assume that the user tries to load from a local file
//
string deltas, deletions;
if (load_from_file(filename, user_id, error_msg, deltas, deletions)
!= true) {
XLOG_TRACE(_verbose, "Failed to load file %s: %s",
filename.c_str(), error_msg.c_str());
return false;
}
XLOG_TRACE(_verbose, "Loading file %s", filename.c_str());
//
// Commit the changes
//
CallBack cb2 = callback(this,
&MasterConfigTree::load_config_commit_changes_cb,
deltas, deletions, cb);
if (change_config(user_id, cb2, error_msg) != true) {
discard_changes();
return false;
}
return true;
} while (false);
//
// Create a temporary file that would be used by the external
// program to copy the configuration to.
//
do {
FILE* fp;
// Create the file
fp = xorp_make_temporary_file("", "xorp_rtrmgr_tmp_config_file",
_tmp_config_filename, error_msg);
if (fp == NULL) {
error_msg = c_format("Cannot load the configuration file: "
"cannot create a temporary filename: %s",
error_msg.c_str());
_tmp_config_filename = "";
return false;
}
// Set the file permissions
if (set_config_file_permissions(fp, user_id, error_msg) != true) {
error_msg = c_format("Cannot load the configuration file: %s",
error_msg.c_str());
// Close and remove the file
fclose(fp);
remove_tmp_config_file();
return false;
}
//
// Close the file descriptor, because we don't need it.
// Note, that the created file remains on the filesystem,
// so it is guaranteed to be unique when we need to use it again.
//
fclose(fp);
break;
} while (false);
//
// Check whether the current configuration already contains
// rtrmgr configuration.
//
list<string> path;
path.push_back(RTRMGR_CONFIG_NODE);
if (find_node(path) != NULL) {
_rtrmgr_config_node_found = true;
} else {
_rtrmgr_config_node_found = false;
}
//
// Add the temporary load file to the configuration
//
if (apply_deltas(user_id, load_file_config,
true /* provisional_change */,
false /* preserve_node_id */,
error_msg)
!= true) {
error_msg = c_format("Cannot load the configuration file: %s",
error_msg.c_str());
remove_tmp_config_file();
discard_changes();
return false;
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
add_default_children();
// Save the name of the temporary file in the local variable
if (root_node().set_variable(RTRMGR_CONFIG_FILENAME_VARNAME,
_tmp_config_filename.c_str())
!= true) {
error_msg = c_format("Cannot load the configuration file: "
"cannot store temporary filename %s in internal "
"variable %s",
_tmp_config_filename.c_str(),
RTRMGR_CONFIG_FILENAME_VARNAME.c_str());
remove_tmp_config_file();
return false;
}
//
// Specify the callback that will be invoked after we have read the new
// configuration file so we can continue from there.
//
CallBack load_cb;
load_cb = callback(this, &MasterConfigTree::load_config_file_received_cb,
filename, user_id, cb);
if (change_config(user_id, load_cb, error_msg) != true) {
remove_tmp_config_file();
discard_changes();
return false;
}
return true;
}
void
MasterConfigTree::load_config_file_received_cb(bool success,
string error_msg,
string filename,
uid_t user_id,
ConfigLoadCallBack cb)
{
string dummy_error_msg, dummy_deltas, dummy_deletions;
if (! success) {
XLOG_TRACE(_verbose, "Failed to load file %s: %s",
filename.c_str(), error_msg.c_str());
remove_tmp_config_file();
discard_changes();
cb->dispatch(success, error_msg, dummy_deltas, dummy_deletions);
return;
}
//
// Check everything really worked, and finalize the commit when
// the file was loaded.
//
if (check_commit_status(error_msg) == false) {
XLOG_TRACE(_verbose, "Check commit status indicates failure: %s",
error_msg.c_str());
remove_tmp_config_file();
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
XLOG_TRACE(_verbose, "Received file %s", filename.c_str());
//
// Get the string with the name of the temporary file with the
// new configuration.
//
string rtrmgr_config_filename;
if (root_node().expand_variable(RTRMGR_CONFIG_FILENAME_VARNAME,
rtrmgr_config_filename,
false)
!= true) {
success = false;
error_msg = c_format("internal variable %s not found",
RTRMGR_CONFIG_FILENAME_VARNAME.c_str());
XLOG_TRACE(_verbose, "Failed to load file %s: %s",
filename.c_str(), error_msg.c_str());
error_msg = c_format("Cannot load configuration file %s because of "
"internal error: %s",
filename.c_str(), error_msg.c_str());
remove_tmp_config_file();
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
//
// Read the configuration from the temporary file
//
string rtrmgr_config_value;
if (read_file(rtrmgr_config_value, rtrmgr_config_filename, error_msg)
!= true) {
success = false;
XLOG_TRACE(_verbose, "Failed to load file %s: %s",
filename.c_str(), error_msg.c_str());
error_msg = c_format("Cannot load configuration file %s because "
"cannot read temporary file %s with the "
"configuration: %s",
filename.c_str(), rtrmgr_config_filename.c_str(),
error_msg.c_str());
remove_tmp_config_file();
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
//
// Remove the temporary file with the configuration
//
remove_tmp_config_file();
//
// Create the string that is used to delete the temporary added
// load file from the configuration.
//
string delete_load_file_config;
if (_rtrmgr_config_node_found) {
delete_load_file_config = c_format(RTRMGR_LOAD_FILE_CONFIG.c_str(),
filename.c_str());
} else {
delete_load_file_config = RTRMGR_CONFIG;
}
if (apply_deletions(user_id, delete_load_file_config,
/* provisional_change */ true, error_msg)
!= true) {
error_msg = c_format("Cannot load the configuration file because of "
"internal error: %s", error_msg.c_str());
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
add_default_children();
//
// Specify the callback that will be invoked after we have cleaned-up
// so we can continue from there.
//
bool orig_success = success;
string orig_error_msg = error_msg;
CallBack cleanup_cb;
cleanup_cb = callback(this, &MasterConfigTree::load_config_file_cleanup_cb,
orig_success, orig_error_msg, rtrmgr_config_value,
filename, user_id, cb);
if (change_config(user_id, cleanup_cb, error_msg) != true) {
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
}
void
MasterConfigTree::load_config_file_cleanup_cb(bool success,
string error_msg,
bool orig_success,
string orig_error_msg,
string rtrmgr_config_value,
string filename,
uid_t user_id,
ConfigLoadCallBack cb)
{
string deltas, deletions;
string dummy_deltas, dummy_deletions;
if (! orig_success) {
XLOG_TRACE(_verbose, "Failed to load file %s: %s",
filename.c_str(), orig_error_msg.c_str());
discard_changes();
cb->dispatch(orig_success, orig_error_msg, dummy_deltas,
dummy_deletions);
return;
}
if (! success) {
XLOG_TRACE(_verbose, "Failed to load file %s: %s",
filename.c_str(), error_msg.c_str());
discard_changes();
cb->dispatch(success, error_msg, dummy_deltas, dummy_deletions);
return;
}
//
// Check everything really worked, and finalize the commit when
// we cleaned up the file that was temporarily added.
//
if (check_commit_status(error_msg) == false) {
XLOG_TRACE(_verbose, "Check commit status indicates failure: %s",
error_msg.c_str());
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
XLOG_TRACE(_verbose, "Cleanup completed after receiving file %s",
filename.c_str());
//
// Test out parsing the config on a new config tree to detect any
// parse errors before we reconfigure ourselves with the new config.
//
MasterConfigTree new_tree(_template_tree, _verbose);
if (new_tree.parse(rtrmgr_config_value, filename, error_msg) != true) {
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
new_tree.add_default_children();
//
// Ok, so the new config parses. Now we need to figure out how it
// differs from the existing config so we don't need to modify
// anything that hasn't changed.
//
MasterConfigTree delta_tree(_template_tree, _verbose);
MasterConfigTree deletion_tree(_template_tree, _verbose);
diff_configs(new_tree, delta_tree, deletion_tree);
if (! root_node().merge_deltas(user_id, delta_tree.const_root_node(),
true /* provisional_change */,
false /* preserve_node_id */,
error_msg)) {
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
if (! root_node().merge_deletions(user_id, deletion_tree.const_root_node(),
true /* provisional_change */,
error_msg)) {
discard_changes();
cb->dispatch(false, error_msg, dummy_deltas, dummy_deletions);
return;
}
// Pass these back out so we can notify other users of the change
deltas = delta_tree.show_unannotated_tree(/*numbered*/ true);
deletions = deletion_tree.show_unannotated_tree(/*numbered*/ true);
//
// Commit the changes
//
CallBack cb2 = callback(this,
&MasterConfigTree::load_config_commit_changes_cb,
deltas, deletions, cb);
if (change_config(user_id, cb2, error_msg) != true) {
discard_changes();
cb->dispatch(false, error_msg, deltas, deletions);
return;
}
}
void
MasterConfigTree::load_config_commit_changes_cb(bool success,
string error_msg,
string deltas,
string deletions,
ConfigLoadCallBack cb)
{
cb->dispatch(success, error_msg, deltas, deletions);
}
bool
MasterConfigTree::load_from_file(const string& filename, uid_t user_id,
string& error_msg, string& deltas,
string& deletions)
{
string dummy_error_msg;
string full_filename = config_full_filename(filename);
//
// We run load_from_file as the UID of the user making the request
// and as group xorp. This prevents users using the rtrmgr to
// attempt to load files they wouldn't normally have had the
// permission to read. Otherwise it's possible that they could
// load configs they shouldn't have access to, or get parts of
// protected files reported to them in error messages.
//
//
// Set the effective group to "xorp" and the effective user ID to the
// uid of the user that sent the request.
//
if (! _is_xorp_gid_set) {
error_msg = "Group \"xorp\" does not exist on this system";
return false;
}
_exec_id.set_uid(user_id);
_exec_id.set_gid(_xorp_gid);
_exec_id.save_current_exec_id();
if (_exec_id.set_effective_exec_id(error_msg) != XORP_OK) {
_exec_id.restore_saved_exec_id(dummy_error_msg);
return false;
}
string configuration;
if (! read_file(configuration, full_filename, error_msg)) {
_exec_id.restore_saved_exec_id(dummy_error_msg);
return false;
}
// Revert UID and GID now we've done reading the file
_exec_id.restore_saved_exec_id(dummy_error_msg);
//
// Test out parsing the config on a new config tree to detect any
// parse errors before we reconfigure ourselves with the new config.
//
MasterConfigTree new_tree(_template_tree, _verbose);
if (new_tree.parse(configuration, full_filename, error_msg) != true) {
return false;
}
//
// Go through the config tree, and create nodes for any defaults
// specified in the template tree that aren't already configured.
//
new_tree.add_default_children();
//
// Ok, so the new config parses. Now we need to figure out how it
// differs from the existing config so we don't need to modify
// anything that hasn't changed.
//
MasterConfigTree delta_tree(_template_tree, _verbose);
MasterConfigTree deletion_tree(_template_tree, _verbose);
diff_configs(new_tree, delta_tree, deletion_tree);
if (! root_node().merge_deltas(user_id, delta_tree.const_root_node(),
true /* provisional_change */,
false /* preserve_node_id */,
error_msg)) {
discard_changes();
return false;
}
if (! root_node().merge_deletions(user_id, deletion_tree.const_root_node(),
true /* provisional_change */,
error_msg)) {
discard_changes();
return false;
}
// Pass these back out so we can notify other users of the change
deltas = delta_tree.show_unannotated_tree(/*numbered*/ true);
deletions = deletion_tree.show_unannotated_tree(/*numbered*/ true);
//
// The config is loaded. We haven't yet committed it, but that
// happens elsewhere so we can handle the callbacks correctly.
//
return true;
}
void
MasterConfigTree::set_config_directory(const string& config_directory)
{
_config_directory = config_directory;
}
void
MasterConfigTree::diff_configs(const MasterConfigTree& new_tree,
MasterConfigTree& delta_tree,
MasterConfigTree& deletion_tree)
{
//
// Clone the existing config tree into the deletion tree.
// Clone the new config tree into the delta tree.
//
deletion_tree = *((MasterConfigTree*)(this));
delta_tree = new_tree;
deletion_tree.retain_deletion_nodes(new_tree, false);
delta_tree.retain_different_nodes(*((ConfigTree*)(this)), true);
debug_msg("=========================================================\n");
debug_msg("ORIG:\n");
debug_msg("%s", tree_str().c_str());
debug_msg("=========================================================\n");
debug_msg("NEW:\n");
debug_msg("%s", new_tree.tree_str().c_str());
debug_msg("=========================================================\n");
debug_msg("=========================================================\n");
debug_msg("DELTAS:\n");
debug_msg("%s", delta_tree.tree_str().c_str());
debug_msg("=========================================================\n");
debug_msg("DELETIONS:\n");
debug_msg("%s", deletion_tree.tree_str().c_str());
debug_msg("=========================================================\n");
}
bool
MasterConfigTree::module_config_start(const string& module_name,
string& error_msg)
{
ModuleCommand *cmd = _template_tree->find_module(module_name);
if (cmd == NULL) {
error_msg = c_format("Module %s is not registered with the "
"TemplateTree, but is needed to satisfy a "
"dependency\n",
module_name.c_str());
return false;
}
if (_task_manager->add_module(*cmd, error_msg) != XORP_OK) {
return false;
}
return true;
}
#if 0 // TODO
bool
MasterConfigTree::module_shutdown(const string& module_name,
string& error_msg)
{
ModuleCommand *cmd = _template_tree->find_module(module_name);
if (cmd == NULL) {
error_msg = c_format("Module %s is not registered with the "
"TemplateTree, but is needed to satisfy a "
"dependency\n",
module_name.c_str());
return false;
}
_task_manager->shutdown_module(*cmd);
return true;
}
#endif // 0
syntax highlighted by Code2HTML, v. 0.9.1