// -*- 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/module_manager.cc,v 1.64 2007/02/16 22:47:23 pavlin Exp $" #include "rtrmgr_module.h" #include "libxorp/xorp.h" #include "libxorp/xlog.h" #include "libxorp/debug.h" #include "libxorp/utils.hh" #ifdef HAVE_GLOB_H #include #elif defined(HOST_OS_WINDOWS) #include "glob_win32.h" #endif #ifdef HAVE_SYS_STAT_H #include #endif #include "module_manager.hh" #include "conf_tree_node.hh" #include "master_conf_tree.hh" #include "template_commands.hh" #include "main_rtrmgr.hh" #include "util.hh" #ifdef HOST_OS_WINDOWS #define stat _stat #endif const TimeVal Module::SHUTDOWN_TIMEOUT_TIMEVAL = TimeVal(2, 0); Module::Module(ModuleManager& mmgr, const string& name, const string& path, const string& expath, bool verbose) : GenericModule(name), _mmgr(mmgr), _path(path), _expath(expath), _do_exec(false), _verbose(verbose) { } Module::~Module() { XLOG_ASSERT(_status == MODULE_NOT_STARTED); } void Module::new_status(ModuleStatus new_status) { if (new_status == _status) return; ModuleStatus old_status = _status; _status = new_status; _mmgr.module_status_changed(_name, old_status, new_status); } string Module::str() const { string s = c_format("Module %s, path %s\n", _name.c_str(), _path.c_str()); if (_status != MODULE_NOT_STARTED && _status != MODULE_FAILED) s += c_format("Module is running\n"); else s += "Module is not running\n"; return s; } int Module::execute(bool do_exec, bool is_verification, XorpCallback1::RefPtr cb) { string error_msg; _terminate_cb.release(); if (! is_verification) XLOG_INFO("Executing module: %s (%s)", _name.c_str(), _path.c_str()); _do_exec = do_exec; if (! _do_exec) { cb->dispatch(true); return XORP_OK; } if (_expath.empty()) { // No process to execute new_status(MODULE_STARTUP); cb->dispatch(true); return XORP_OK; } // Check if a process with the same path is running already if (_mmgr.find_process_by_path(_expath) == NULL) { if (_mmgr.execute_process(_expath, error_msg) != XORP_OK) { cb->dispatch(false); return XORP_ERROR; } } new_status(MODULE_STARTUP); cb->dispatch(true); return XORP_OK; } int Module::restart() { XLOG_INFO("Restarting module %s ...", name().c_str()); XorpCallback1::RefPtr execute_cb = callback(this, &Module::module_restart_cb); if (execute(true, false, execute_cb) != XORP_OK) { XLOG_ERROR("Failed to restart module %s.", name().c_str()); return (XORP_ERROR); } MasterConfigTree* mct = module_manager().master_config_tree(); ConfigTreeNode* ctn = mct->find_config_module(name()); if (ctn != NULL) { XLOG_INFO("Configuring module %s ...", name().c_str()); ctn->mark_subtree_as_uncommitted(); mct->execute(); } return (XORP_OK); } void Module::terminate(XorpCallback0::RefPtr cb) { debug_msg("Module::terminate() : %s\n", _name.c_str()); _terminate_cb.release(); if (! _do_exec) { cb->dispatch(); return; } if (_status == MODULE_NOT_STARTED) { cb->dispatch(); return; } if (_status == MODULE_FAILED) { new_status(MODULE_NOT_STARTED); cb->dispatch(); return; } XLOG_INFO("Terminating module: %s", _name.c_str()); if (_expath.empty()) { // Nothing to terminate new_status(MODULE_NOT_STARTED); cb->dispatch(); return; } // // Find whether this is the last module running within this process // list module_list = _mmgr.find_running_modules_by_path(_expath); if (module_list.size() > 1) { // Not the last module within this process, hence we are done new_status(MODULE_NOT_STARTED); cb->dispatch(); return; } // // Kill the process // XLOG_INFO("Killing module: %s", _name.c_str()); new_status(MODULE_SHUTTING_DOWN); _terminate_cb = cb; ModuleManager::Process* process = _mmgr.find_process_by_path(_expath); XLOG_ASSERT(process != NULL); process->terminate(); _shutdown_timer = _mmgr.eventloop().new_oneoff_after( Module::SHUTDOWN_TIMEOUT_TIMEVAL, callback(this, &Module::terminate_with_prejudice, cb)); } void Module::terminate_with_prejudice(XorpCallback0::RefPtr cb) { debug_msg("terminate_with_prejudice\n"); _terminate_cb.release(); if (_status == MODULE_NOT_STARTED) { cb->dispatch(); return; } if (_status == MODULE_FAILED) { new_status(MODULE_NOT_STARTED); cb->dispatch(); return; } if (_expath.empty()) { // Nothing to terminate new_status(MODULE_NOT_STARTED); cb->dispatch(); return; } // // Kill the process with prejudice // XLOG_INFO("Killing module with prejudice: %s", _name.c_str()); new_status(MODULE_NOT_STARTED); _terminate_cb = cb; ModuleManager::Process* process = _mmgr.find_process_by_path(_expath); XLOG_ASSERT(process != NULL); process->terminate_with_prejudice(); // Give it a couple more seconds to really go away _shutdown_timer = _mmgr.eventloop().new_oneoff_after( Module::SHUTDOWN_TIMEOUT_TIMEVAL, callback(this, &Module::terminate_with_prejudice, cb)); } void Module::module_restart_cb(bool success) { string error_msg; // Do we think we managed to start it? if (success == false) { new_status(MODULE_FAILED); return; } // Is it still running? if (_status == MODULE_FAILED) { error_msg = c_format("%s: module startup failed", _expath.c_str()); XLOG_ERROR("%s", error_msg.c_str()); return; } if (_status == MODULE_STARTUP) { new_status(MODULE_INITIALIZING); } } void Module::module_exited(bool success, bool is_signal_terminated, int term_signal, bool is_coredumped) { UNUSED(term_signal); if (success) { XLOG_INFO("Module normal exit: %s", _name.c_str()); new_status(MODULE_NOT_STARTED); return; } if (is_signal_terminated) { if (_status == MODULE_SHUTTING_DOWN) { XLOG_INFO("Module killed during shutdown: %s", _name.c_str()); new_status(MODULE_NOT_STARTED); if (! _terminate_cb.is_empty()) _terminate_cb->dispatch(); _terminate_cb.release(); return; } if (is_coredumped) { XLOG_INFO("Module coredumped: %s", _name.c_str()); } else { // We don't know why it was killed. XLOG_INFO("Module abnormally killed: %s", _name.c_str()); } new_status(MODULE_FAILED); if (module_manager().do_restart()) { // // Restart the module. // XXX: if the child was killed by SIGTERM or SIGKILL, then // DO NOT restart it, because probably it was killed for a reason. // switch (term_signal) { #ifndef HOST_OS_WINDOWS case SIGTERM: case SIGKILL: break; #endif // HOST_OS_WINDOWS default: restart(); break; } } return; } XLOG_INFO("Module abnormal exit: %s", _name.c_str()); new_status(MODULE_FAILED); if (module_manager().do_restart()) restart(); } void Module::module_stopped(int stop_signal) { UNUSED(stop_signal); XLOG_INFO("Module stalled: %s", _name.c_str()); new_status(MODULE_STALLED); } ModuleManager::ModuleManager(EventLoop& eventloop, Rtrmgr& rtrmgr, bool do_restart, bool verbose, const string& xorp_root_dir) : GenericModuleManager(eventloop, verbose), _rtrmgr(rtrmgr), _master_config_tree(NULL), _do_restart(do_restart), _verbose(verbose), _xorp_root_dir(xorp_root_dir) { } ModuleManager::~ModuleManager() { shutdown(); } int ModuleManager::expand_execution_path(const string& path, string& expath, string& error_msg) { if (path.empty()) { // Empty path: no program should be executed expath = path; return XORP_OK; } if (! is_absolute_path(path, true)) { // // The path to the module doesn't starts from the user home directory // and is not an absolute path (in UNIX, DOS or NT UNC form). // // Add the XORP root path to the front expath = xorp_root_dir() + PATH_DELIMITER_STRING + path; } #ifdef HOST_OS_WINDOWS // Assume the path is still in UNIX format at this point and needs to // be converted to the native format. expath = unix_path_to_native(expath); #endif if (! is_absolute_path(expath, false)) { debug_msg("calling glob\n"); // we're going to call glob, but don't want to allow wildcard expansion for (size_t i = 0; i < expath.length(); i++) { char c = expath[i]; if ((c == '*') || (c == '?') || (c == '[')) { error_msg = c_format("%s: bad filename", expath.c_str()); return XORP_ERROR; } } glob_t pglob; glob(expath.c_str(), 0, NULL, &pglob); if (pglob.gl_pathc != 1) { error_msg = c_format("%s: file does not exist", expath.c_str()); return XORP_ERROR; } expath = pglob.gl_pathv[0]; globfree(&pglob); } expath += EXECUTABLE_SUFFIX; struct stat sb; if (stat(expath.c_str(), &sb) < 0) { switch (errno) { case ENOTDIR: error_msg = "a component of the path prefix is not a directory"; break; case ENOENT: error_msg = "file does not exist"; break; case EACCES: error_msg = "permission denied"; break; #ifdef ELOOP case ELOOP: error_msg = "too many symbolic links"; break; #endif default: error_msg = "unknown error accessing file"; } error_msg = c_format("%s: %s", expath.c_str(), error_msg.c_str()); return XORP_ERROR; } return XORP_OK; } bool ModuleManager::new_module(const string& module_name, const string& path, string& error_msg) { string expath; debug_msg("ModuleManager::new_module %s\n", module_name.c_str()); if (expand_execution_path(path, expath, error_msg) != XORP_OK) { return false; } Module* module = new Module(*this, module_name, path, expath, _verbose); if (store_new_module(module, error_msg) != true) { delete module; return false; } XLOG_TRACE(_verbose, "New module: %s (%s)", module_name.c_str(), path.c_str()); return true; } int ModuleManager::start_module(const string& module_name, bool do_exec, bool is_verification, XorpCallback1::RefPtr cb) { Module* module; module = dynamic_cast(find_module(module_name)); XLOG_ASSERT(module != NULL); return (module->execute(do_exec, is_verification, cb)); } int ModuleManager::kill_module(const string& module_name, XorpCallback0::RefPtr cb) { Module* module; module = dynamic_cast(find_module(module_name)); XLOG_ASSERT(module != NULL); module->terminate(cb); return XORP_OK; } bool ModuleManager::module_is_running(const string& module_name) const { const Module* module; module = dynamic_cast(const_find_module(module_name)); if (module == NULL) return false; return (module->status() == Module::MODULE_RUNNING); } bool ModuleManager::module_has_started(const string& module_name) const { const Module* module; module = dynamic_cast(const_find_module(module_name)); if (module == NULL) return false; return (module->status() != Module::MODULE_NOT_STARTED); } void ModuleManager::shutdown() { debug_msg("ModuleManager::shutdown\n"); map::iterator iter; for (iter = _modules.begin(); iter != _modules.end(); ++iter) { Module *module = dynamic_cast(iter->second); XLOG_ASSERT(module != NULL); module->terminate(callback(this, &ModuleManager::module_shutdown_cb, module->name())); } } void ModuleManager::module_shutdown_cb(string module_name) { // TODO: anything that needs to be done here? UNUSED(module_name); } bool ModuleManager::is_shutdown_completed() const { bool completed = true; map::const_iterator iter; for (iter = _modules.begin(); iter != _modules.end(); ++iter) { if (iter->second->status() != Module::MODULE_NOT_STARTED) { completed = false; break; } } return completed; } void ModuleManager::module_status_changed(const string& module_name, Module::ModuleStatus old_status, Module::ModuleStatus new_status) { UNUSED(old_status); _rtrmgr.module_status_changed(module_name, new_status); } list ModuleManager::get_module_names() const { list result; map::const_iterator iter; for (iter = _modules.begin(); iter != _modules.end(); ++iter) { result.push_back(iter->first); } return (result); } list ModuleManager::find_running_modules_by_path(const string& expath) { list modules_list; map::const_iterator iter; for (iter = _modules.begin(); iter != _modules.end(); ++iter) { Module* module = dynamic_cast(iter->second); XLOG_ASSERT(module != NULL); if (module->expath() != expath) continue; switch (module->status()) { case Module::MODULE_STARTUP: case Module::MODULE_INITIALIZING: case Module::MODULE_RUNNING: case Module::MODULE_STALLED: case Module::MODULE_SHUTTING_DOWN: modules_list.push_back(module); break; case Module::MODULE_FAILED: case Module::MODULE_NOT_STARTED: case Module::NO_SUCH_MODULE: break; } } return (modules_list); } int ModuleManager::execute_process(const string& expath, string& error_msg) { if (expath.empty()) return (XORP_OK); // XXX: nothing ro execute if (_expath2process.find(expath) != _expath2process.end()) return (XORP_OK); // XXX: process is already executed ModuleManager::Process* process; process = new ModuleManager::Process(*this, expath); _expath2process.insert(make_pair(expath, process)); if (process->startup(error_msg) != XORP_OK) { _expath2process.erase(expath); delete process; return (XORP_ERROR); } return (XORP_OK); } void ModuleManager::process_exited(const string& expath, bool success, bool is_signal_terminated, int term_signal, bool is_coredumped) { list modules_list; list::iterator module_iter; map::iterator process_iter; // // Inform all modules that the corresponding process has exited // modules_list = find_running_modules_by_path(expath); for (module_iter = modules_list.begin(); module_iter != modules_list.end(); ++module_iter) { Module* module = *module_iter; module->module_exited(success, is_signal_terminated, term_signal, is_coredumped); } // // Remove the process-related state // process_iter = _expath2process.find(expath); XLOG_ASSERT(process_iter != _expath2process.end()); Process* process = process_iter->second; _expath2process.erase(process_iter); delete process; } void ModuleManager::process_stopped(const string& expath, int stop_signal) { list modules_list; list::iterator iter; // // Inform all modules that the corresponding process has stopped // modules_list = find_running_modules_by_path(expath); for (iter = modules_list.begin(); iter != modules_list.end(); ++iter) { Module* module = *iter; module->module_stopped(stop_signal); } } ModuleManager::Process* ModuleManager::find_process_by_path(const string& expath) { map::iterator iter; iter = _expath2process.find(expath); if (iter == _expath2process.end()) return (NULL); return (iter->second); } ModuleManager::Process::Process(ModuleManager& mmgr, const string& expath) : _mmgr(mmgr), _expath(expath), _run_command(NULL) { } ModuleManager::Process::~Process() { if (_run_command != NULL) { delete _run_command; _run_command = NULL; } } int ModuleManager::Process::startup(string& error_msg) { list empty_args; XLOG_ASSERT(_run_command == NULL); _run_command = new RunCommand( _mmgr.eventloop(), _expath, empty_args, // XXX: no arguments allowed callback(this, &ModuleManager::Process::stdout_cb), callback(this, &ModuleManager::Process::stderr_cb), callback(this, &ModuleManager::Process::done_cb), true); _run_command->set_stopped_cb( callback(this, &ModuleManager::Process::stopped_cb)); if (_run_command->execute() != XORP_OK) { delete _run_command; _run_command = NULL; error_msg = c_format("Could not execute program %s", _expath.c_str()); return (XORP_ERROR); } return (XORP_OK); } void ModuleManager::Process::terminate() { if (_run_command != NULL) _run_command->terminate(); } void ModuleManager::Process::terminate_with_prejudice() { if (_run_command != NULL) _run_command->terminate_with_prejudice(); } void ModuleManager::Process::stdout_cb(RunCommand* run_command, const string& output) { XLOG_ASSERT(run_command == _run_command); // XXX: output the message from the child process to stdout as-is fprintf(stdout, "%s", output.c_str()); } void ModuleManager::Process::stderr_cb(RunCommand* run_command, const string& output) { XLOG_ASSERT(run_command == _run_command); // XXX: output the message from the child process to stderr as-is fprintf(stderr, "%s", output.c_str()); } void ModuleManager::Process::done_cb(RunCommand* run_command, bool success, const string& error_msg) { XLOG_ASSERT(run_command == _run_command); bool is_signal_terminated = run_command->is_signal_terminated(); int term_signal = run_command->term_signal(); bool is_coredumped = run_command->is_coredumped(); if (! success) XLOG_ERROR("%s", error_msg.c_str()); if (_run_command != NULL) { delete _run_command; _run_command = NULL; } // // XXX: Must be the last action because it will delete the object itself. // _mmgr.process_exited(_expath, success, is_signal_terminated, term_signal, is_coredumped); } void ModuleManager::Process::stopped_cb(RunCommand* run_command, int stop_signal) { XLOG_ASSERT(run_command == _run_command); _mmgr.process_stopped(_expath, stop_signal); }