// -*- 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 <glob.h>
#elif defined(HOST_OS_WINDOWS)
#include "glob_win32.h"
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#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<void, bool>::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<void, bool>::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<void>::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*> 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<void>::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<void, bool>::RefPtr cb)
{
    Module* module;

    module = dynamic_cast<Module *>(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<void>::RefPtr cb)
{
    Module* module;

    module = dynamic_cast<Module *>(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 Module *>(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 Module *>(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<string, GenericModule *>::iterator iter;
    for (iter = _modules.begin(); iter != _modules.end(); ++iter) {
	Module *module = dynamic_cast<Module *>(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<string, GenericModule *>::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<string>
ModuleManager::get_module_names() const
{
    list<string> result;

    map<string, GenericModule *>::const_iterator iter;
    for (iter = _modules.begin(); iter != _modules.end(); ++iter) {
	result.push_back(iter->first);
    }

    return (result);
}

list<Module *>
ModuleManager::find_running_modules_by_path(const string& expath)
{
    list<Module *> modules_list;

    map<string, GenericModule *>::const_iterator iter;
    for (iter = _modules.begin(); iter != _modules.end(); ++iter) {
	Module* module = dynamic_cast<Module *>(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<Module *> modules_list;
    list<Module *>::iterator module_iter;
    map<string, Process *>::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<Module *> modules_list;
    list<Module *>::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<string, Process *>::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<string> 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);
}


syntax highlighted by Code2HTML, v. 0.9.1