// Copyright (C) 2013-2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <asiolink/io_service_mgr.h>
#include <cc/command_interpreter.h>
#include <process/cfgrpt/config_report.h>
#include <cryptolink/crypto_hash.h>
#include <exceptions/exceptions.h>
#include <config/command_mgr.h>
#include <hooks/hooks_manager.h>
#include <log/logger.h>
#include <log/logger_support.h>
#include <util/encode/encode.h>
#include <util/filesystem.h>
#include <process/daemon.h>
#include <process/d_log.h>
#include <process/d_controller.h>
#include <process/config_base.h>
#include <kea_version.h>
#include <functional>
#include <sstream>
#include <string>
#include <unistd.h>
#include <signal.h>

using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::hooks;
using namespace isc::util;
namespace ph = std::placeholders;

namespace isc {
namespace process {

DControllerBasePtr DControllerBase::controller_;

// Note that the constructor instantiates the controller's primary IOService.
DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
    : app_name_(app_name), bin_name_(bin_name),
      verbose_(false), check_only_(false),
      io_service_(new isc::asiolink::IOService()),
      io_signal_set_() {
}

void
DControllerBase::setController(const DControllerBasePtr& controller) {
    if (controller_) {
        // This shouldn't happen, but let's make sure it can't be done.
        // It represents a programmatic error.
        isc_throw(DControllerBaseError, "Multiple controller instances attempted.");
    }

    controller_ = controller;
}

ConstElementPtr
DControllerBase::parseFile(const std::string&) {
    ConstElementPtr elements;
    return (elements);
}

int
DControllerBase::launch(int argc, char* argv[], const bool test_mode) {

    // Step 1 is to parse the command line arguments.
    try {
        parseArgs(argc, argv);
    } catch (const InvalidUsage& ex) {
        usage(ex.what());
        // rethrow it with an empty message
        isc_throw(InvalidUsage, "");
    }

    setProcName(bin_name_);

    if (isCheckOnly()) {
        checkConfigOnly();
        return (EXIT_SUCCESS);
    }

    // It is important that we set a default logger name because this name
    // will be used when the user doesn't provide the logging configuration
    // in the Kea configuration file.
    Daemon::setDefaultLoggerName(bin_name_);

    // Logger's default configuration depends on whether we are in the
    // verbose mode or not. CfgMgr manages the logger configuration so
    // the verbose mode is set for CfgMgr.
    Daemon::setVerbose(verbose_);

    // Do not initialize logger here if we are running unit tests. It would
    // replace an instance of unit test specific logger.
    if (!test_mode) {
        // Now that we know what the mode flags are, we can init logging.
        Daemon::loggerInit(bin_name_.c_str(), verbose_);
    }

    try {
        checkConfigFile();
    } catch (const std::exception& ex) {
        LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
            .arg(app_name_).arg(ex.what());
        isc_throw(LaunchError, "Launch Failed: " << ex.what());
    }

    try {
        createPIDFile();
    } catch (const DaemonPIDExists& ex) {
        LOG_FATAL(dctl_logger, DCTL_ALREADY_RUNNING)
                  .arg(bin_name_).arg(ex.what());
        isc_throw(LaunchError, "Launch Failed: " << ex.what());
    } catch (const std::exception& ex) {
        LOG_FATAL(dctl_logger, DCTL_PID_FILE_ERROR)
                  .arg(app_name_).arg(ex.what());
        isc_throw(LaunchError, "Launch failed: " << ex.what());
    }

    // Log the starting of the service.
    LOG_INFO(dctl_logger, DCTL_STARTING)
        .arg(app_name_)
        .arg(getpid())
        .arg(VERSION)
        .arg(PACKAGE_VERSION_TYPE);

    // When it is not a stable version dissuade use in production.
    if (std::string(PACKAGE_VERSION_TYPE) == "development") {
        LOG_WARN(dctl_logger, DCTL_DEVELOPMENT_VERSION);
    }

    if (file::amRunningAsRoot()) {
        LOG_WARN(dctl_logger, DCTL_ROOT_USER_SECURITY_WARNING)
                .arg(app_name_);
    }

    try {
        // Step 2 is to create and initialize the application process object.
        initProcess();
    } catch (const std::exception& ex) {
        LOG_FATAL(dctl_logger, DCTL_INIT_PROCESS_FAIL)
                  .arg(app_name_).arg(ex.what());
        isc_throw(ProcessInitError,
                  "Application Process initialization failed: " << ex.what());
    }

    LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_STANDALONE)
        .arg(app_name_);

    // Step 3 is to load configuration from file.
    int rcode;
    ConstElementPtr comment = parseAnswer(rcode, configFromFile());
    if (rcode != 0) {
        LOG_FATAL(dctl_logger, DCTL_CONFIG_FILE_LOAD_FAIL)
                  .arg(app_name_).arg(comment->stringValue());
        isc_throw(ProcessInitError, "Could Not load configuration file: "
                  << comment->stringValue());
    }

    // Note that the controller was started.
    start_ = boost::posix_time::second_clock::universal_time();

    // Everything is clear for launch, so start the application's
    // event loop.
    try {
        // Now that we have a process, we can set up signal handling.
        initSignalHandling();
        runProcess();
    } catch (const std::exception& ex) {
        LOG_FATAL(dctl_logger, DCTL_PROCESS_FAILED)
                  .arg(app_name_).arg(ex.what());
        isc_throw(ProcessRunError,
                  "Application process event loop failed: " << ex.what());
    }

    // All done, so bail out.
    LOG_INFO(dctl_logger, DCTL_SHUTDOWN)
        .arg(app_name_).arg(getpid()).arg(VERSION);

    return (getExitValue());
}

void
DControllerBase::checkConfigOnly() {
    try {
        // We need to initialize logging, in case any error
        // messages are to be printed.
        // This is just a test, so we don't care about lockfile.
        setenv("KEA_LOCKFILE_DIR", "none", 0);
        Daemon::setDefaultLoggerName(bin_name_);
        Daemon::setVerbose(verbose_);
        Daemon::loggerInit(bin_name_.c_str(), verbose_);

        // Check the syntax first.
        std::string config_file = getConfigFile();
        if (config_file.empty()) {
            // Basic sanity check: file name must not be empty.
            isc_throw(InvalidUsage, "JSON configuration file not specified");
        }
        ConstElementPtr whole_config = parseFile(config_file);
        if (!whole_config) {
            // No fallback to fromJSONFile
            isc_throw(InvalidUsage, "No configuration found");
        }
        if (verbose_) {
            std::cerr << "Syntax check OK" << std::endl;
        }

        // Check the logic next.
        ConstElementPtr module_config;
        module_config = whole_config->get(getAppName());
        if (!module_config) {
            isc_throw(InvalidUsage, "Config file " << config_file <<
                      " does not include '" << getAppName() << "' entry");
        }
        if (module_config->getType() != Element::map) {
            isc_throw(InvalidUsage, "Config file " << config_file <<
                      " includes not map '" << getAppName() << "' entry");
        }

        // Handle other (i.e. not application name) objects.
        std::string errmsg = handleOtherObjects(whole_config);
        if (!errmsg.empty()) {
            isc_throw(InvalidUsage, "Config file " << config_file << errmsg);
        }

        // Get an application process object.
        initProcess();

        ConstElementPtr answer = checkConfig(module_config);
        int rcode = 0;
        answer = parseAnswer(rcode, answer);
        if (rcode != 0) {
            isc_throw(InvalidUsage, "Error encountered: "
                      << answer->stringValue());
        }
    } catch (const VersionMessage&) {
        throw;
    } catch (const InvalidUsage&) {
        throw;
    } catch (const std::exception& ex) {
        isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
    }
    return;
}

void
DControllerBase::parseArgs(int argc, char* argv[]) {

    if (argc == 1) {
        isc_throw(InvalidUsage, "");
    }

    // Iterate over the given command line options. If its a stock option
    // ("c" or "d") handle it here.  If its a valid custom option, then
    // invoke customOption.
    int ch;
    optarg = 0;
    opterr = 0;
    optind = 1;
    std::string opts("dvVWc:t:X" + getCustomOpts());

    // Defer exhausting of arguments to the end.
    ExhaustOptions e(argc, argv, opts);

    while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
        switch (ch) {
        case 'd':
            // Enables verbose logging.
            verbose_ = true;
            break;

        case 'v':
            // gather Kea version and throw so main() can catch and return
            // rather than calling exit() here which disrupts gtest.
            isc_throw(VersionMessage, getVersion(false));
            break;

        case 'V':
            // gather Kea version and throw so main() can catch and return
            // rather than calling exit() here which disrupts gtest.
            isc_throw(VersionMessage, getVersion(true));
            break;

        case 'W':
            // gather Kea config report and throw so main() can catch and
            // return rather than calling exit() here which disrupts gtest.
            isc_throw(VersionMessage, isc::detail::getConfigReport());
            break;

        case 'c':
        case 't':
            // config file name
            if (optarg == NULL) {
                isc_throw(InvalidUsage, "configuration file name missing");
            }

            setConfigFile(optarg);

            if (ch == 't') {
                check_only_ = true;
            }
            break;

        case 'X': // relax security checks
            file::PathChecker::enableEnforcement(false);
            break;

        case '?': {
            char const saved_optopt(optopt);
            std::string const saved_optarg(optarg ? optarg : std::string());

            // We hit an invalid option.
            isc_throw(InvalidUsage, "unsupported option: -" << saved_optopt <<
                      (saved_optarg.empty() ? std::string() : " " + saved_optarg));

            break;
            }

        default:
            // We hit a valid custom option
            if (!customOption(ch, optarg)) {
                char const saved_optopt(optopt);
                std::string const saved_optarg(optarg ? optarg : std::string());

                // We hit an invalid option.
                isc_throw(InvalidUsage, "unsupported option: -" << saved_optopt <<
                          (saved_optarg.empty() ? std::string() : " " + saved_optarg));
            }
            break;
        }
    }

    // There was too much information on the command line.
    if (argc > optind) {
        isc_throw(InvalidUsage, "extraneous command line information");
    }
}

bool
DControllerBase::customOption(int /* option */, char* /*optarg*/) {
    // Default implementation returns false.
    return (false);
}

void
DControllerBase::initProcess() {
    LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_INIT_PROCESS)
        .arg(app_name_);

    // Invoke virtual method to instantiate the application process.
    try {
        process_.reset(createProcess());
    } catch (const std::exception& ex) {
        isc_throw(DControllerBaseError, std::string("createProcess failed: ") +
                  ex.what());
    }

    // This is pretty unlikely, but will test for it just to be safe..
    if (!process_) {
        isc_throw(DControllerBaseError, "createProcess returned NULL");
    }

    // Invoke application's init method (Note this call should throw
    // DProcessBaseError if it fails).
    process_->init();
}

ConstElementPtr
DControllerBase::configFromFile() {
    // Rollback any previous staging configuration. For D2, only a
    // logger configuration is used here.
    // We're not using cfgmgr to store logging configuration anymore.
    // isc::dhcp::CfgMgr::instance().rollback();

    // Will hold configuration.
    ConstElementPtr module_config;
    // Will receive configuration result.
    ConstElementPtr answer;
    try {
        std::string config_file = getConfigFile();
        if (config_file.empty()) {
            // Basic sanity check: file name must not be empty.
            isc_throw(BadValue, "JSON configuration file not specified. Please "
                                "use -c command line option.");
        }

        // If parseFile returns an empty pointer, then pass the file onto the
        // original JSON parser.
        ConstElementPtr whole_config = parseFile(config_file);
        if (!whole_config) {
            // Read contents of the file and parse it as JSON
            whole_config = Element::fromJSONFile(config_file, true);
        }

        // Extract derivation-specific portion of the configuration.
        module_config = whole_config->get(getAppName());
        if (!module_config) {
            isc_throw(BadValue, "Config file " << config_file <<
                                " does not include '" <<
                                 getAppName() << "' entry.");
        }
        if (module_config->getType() != Element::map) {
            isc_throw(InvalidUsage, "Config file " << config_file <<
                      " includes not map '" << getAppName() << "' entry");
        }

        // Handle other (i.e. not application name) objects.
        std::string errmsg = handleOtherObjects(whole_config);
        if (!errmsg.empty()) {
            isc_throw(InvalidUsage, "Config file " << config_file << errmsg);
        }

        // Let's configure logging before applying the configuration,
        // so we can log things during configuration process.

        // Temporary storage for logging configuration
        ConfigPtr storage(new ConfigBase());

        // Configure logging to the temporary storage.
        Daemon::configureLogger(module_config, storage);

        // Let's apply the new logging. We do it early, so we'll be able
        // to print out what exactly is wrong with the new config in
        // case of problems.
        storage->applyLoggingCfg();

        answer = updateConfig(module_config);
        // In all cases the right logging configuration is in the context.
        process_->getCfgMgr()->getContext()->applyLoggingCfg();
    } catch (const std::exception& ex) {
        // Rollback logging configuration.
        process_->getCfgMgr()->getContext()->applyLoggingCfg();

        // build an error result
        ConstElementPtr error = createAnswer(CONTROL_RESULT_ERROR,
                 std::string("Configuration parsing failed: ") + ex.what());
        return (error);
    }

    return (answer);
}

void
DControllerBase::runProcess() {
    LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT, DCTL_RUN_PROCESS)
        .arg(app_name_);
    if (!process_) {
        // This should not be possible.
        isc_throw(DControllerBaseError, "Process not initialized");
    }

    // Invoke the application process's run method. This may throw
    // DProcessBaseError
    process_->run();
}

// Instance method for handling new config
ConstElementPtr
DControllerBase::updateConfig(ConstElementPtr new_config) {
    return (process_->configure(new_config, false));
}

// Instance method for checking new config
ConstElementPtr
DControllerBase::checkConfig(ConstElementPtr new_config) {
    return (process_->configure(new_config, true));
}

ConstElementPtr
DControllerBase::configGetHandler(const std::string&,
                                  ConstElementPtr /*args*/) {
    ElementPtr config = process_->getCfgMgr()->getContext()->toElement();
    std::string hash = BaseCommandMgr::getHash(config);
    config->set("hash", Element::create(hash));

    return (createAnswer(CONTROL_RESULT_SUCCESS, config));
}

ConstElementPtr
DControllerBase::configHashGetHandler(const std::string&,
                                      ConstElementPtr /*args*/) {
    ConstElementPtr config = process_->getCfgMgr()->getContext()->toElement();
    std::string hash = BaseCommandMgr::getHash(config);
    ElementPtr params = Element::createMap();
    params->set("hash", Element::create(hash));
    return (createAnswer(CONTROL_RESULT_SUCCESS, params));
}

ConstElementPtr
DControllerBase::configWriteHandler(const std::string&,
                                    ConstElementPtr args) {
    std::string filename;

    if (args) {
        if (args->getType() != Element::map) {
            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
        }
        ConstElementPtr filename_param = args->get("filename");
        if (filename_param) {
            if (filename_param->getType() != Element::string) {
                return (createAnswer(CONTROL_RESULT_ERROR,
                                     "passed parameter 'filename' "
                                     "is not a string"));
            }
            filename = filename_param->stringValue();
        }
    }

    if (filename.empty()) {
        // filename parameter was not specified, so let's use
        // whatever we remember
        filename = getConfigFile();
        if (filename.empty()) {
            return (createAnswer(CONTROL_RESULT_ERROR,
                                 "Unable to determine filename."
                                 "Please specify filename explicitly."));
        }
    } else {
        try {
            checkWriteConfigFile(filename);
        } catch (const isc::Exception& ex) {
            std::ostringstream msg;
            msg << "not allowed to write config into " << filename
                << ": " << ex.what();
            return (createAnswer(CONTROL_RESULT_ERROR, msg.str()));
        }
    }

    // Ok, it's time to write the file.
    size_t size = 0;

    try {
        ElementPtr cfg = process_->getCfgMgr()->getContext()->toElement();
        size = writeConfigFile(filename, cfg);
    } catch (const isc::Exception& ex) {
        return (createAnswer(CONTROL_RESULT_ERROR,
                             std::string("Error during config-write:")
                             + ex.what()));
    }
    if (size == 0) {
        return (createAnswer(CONTROL_RESULT_ERROR,
                             "Error writing configuration to " + filename));
    }

    // Ok, it's time to return the successful response.
    ElementPtr params = Element::createMap();
    params->set("size", Element::create(static_cast<long long>(size)));
    params->set("filename", Element::create(filename));

    return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
                         + filename + " successful", params));
}

std::string
DControllerBase::handleOtherObjects(ConstElementPtr args) {
    // Check obsolete or unknown (aka unsupported) objects.
    const std::string& app_name = getAppName();
    std::string errmsg;
    for (auto const& obj : args->mapValue()) {
        const std::string& obj_name = obj.first;
        if (obj_name == app_name) {
            continue;
        }
        LOG_ERROR(dctl_logger, DCTL_CONFIG_DEPRECATED)
            .arg("'" + obj_name + "', defining anything in global level besides '"
                 + app_name + "' is no longer supported.");
        if (errmsg.empty()) {
            errmsg = " contains unsupported '" + obj_name + "' parameter";
        } else {
            errmsg += " (and '" + obj_name + "')";
        }
    }
    return (errmsg);
}

ConstElementPtr
DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
    const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
    ConstElementPtr module_config;
    std::string app_name = getAppName();
    std::string message;

    // Command arguments are expected to be:
    // { "Module": { ... } }
    if (!args) {
        message = "Missing mandatory 'arguments' parameter.";
    } else {
        module_config = args->get(app_name);
        if (!module_config) {
            message = "Missing mandatory '" + app_name + "' parameter.";
        } else if (module_config->getType() != Element::map) {
            message = "'" + app_name + "' parameter expected to be a map.";
        }
    }

    if (message.empty()) {
        // Handle other (i.e. not application name) objects.
        std::string errmsg = handleOtherObjects(args);
        if (!errmsg.empty()) {
            message = "'arguments' parameter" + errmsg;
        }
    }

    if (!message.empty()) {
        // Something is amiss with arguments, return a failure response.
        ConstElementPtr result = isc::config::createAnswer(status_code,
                                                           message);
        return (result);
    }

    // We are starting the configuration process so we should remove any
    // staging configuration that has been created during previous
    // configuration attempts.
    // We're not using cfgmgr to store logging information anymore.
    // isc::dhcp::CfgMgr::instance().rollback();

    // Now we check the server proper.
    return (checkConfig(module_config));
}

ConstElementPtr
DControllerBase::configReloadHandler(const std::string&, ConstElementPtr) {
    // Add reload in message?
    return (configFromFile());
}

ConstElementPtr
DControllerBase::configSetHandler(const std::string&, ConstElementPtr args) {
    const int status_code = CONTROL_RESULT_ERROR; // 1 indicates an error
    ConstElementPtr module_config;
    std::string app_name = getAppName();
    std::string message;

    // Command arguments are expected to be:
    // { "Module": { ... } }
    if (!args) {
        message = "Missing mandatory 'arguments' parameter.";
    } else {
        module_config = args->get(app_name);
        if (!module_config) {
            message = "Missing mandatory '" + app_name + "' parameter.";
        } else if (module_config->getType() != Element::map) {
            message = "'" + app_name + "' parameter expected to be a map.";
        }
    }

    if (!message.empty()) {
        // Something is amiss with arguments, return a failure response.
        ConstElementPtr result = isc::config::createAnswer(status_code,
                                                           message);
        return (result);
    }

    try {

        // Handle other (i.e. not application name) objects.
        handleOtherObjects(args);

        // We are starting the configuration process so we should remove any
        // staging configuration that has been created during previous
        // configuration attempts.
        // We're not using cfgmgr to store logging information anymore.
        // isc::dhcp::CfgMgr::instance().rollback();

        // Temporary storage for logging configuration
        ConfigPtr storage(new ConfigBase());

        // Configure logging to the temporary storage.
        Daemon::configureLogger(module_config, storage);

        // Let's apply the new logging. We do it early, so we'll be able
        // to print out what exactly is wrong with the new config in
        // case of problems.
        storage->applyLoggingCfg();

        ConstElementPtr answer = updateConfig(module_config);
        int rcode = 0;
        parseAnswer(rcode, answer);
        // In all cases the right logging configuration is in the context.
        process_->getCfgMgr()->getContext()->applyLoggingCfg();
        return (answer);
    } catch (const std::exception& ex) {
        // Rollback logging configuration.
        process_->getCfgMgr()->getContext()->applyLoggingCfg();

        // build an error result
        ConstElementPtr error = createAnswer(CONTROL_RESULT_ERROR,
                 std::string("Configuration parsing failed: ") + ex.what());
        return (error);
    }
}

ConstElementPtr
DControllerBase::serverTagGetHandler(const std::string&, ConstElementPtr) {
    const std::string& tag = process_->getCfgMgr()->getContext()->getServerTag();
    ElementPtr response = Element::createMap();
    response->set("server-tag", Element::create(tag));

    return (createAnswer(CONTROL_RESULT_SUCCESS, response));
}

ConstElementPtr
DControllerBase::statusGetHandler(const std::string&, ConstElementPtr) {
    ElementPtr status = Element::createMap();
    status->set("pid", Element::create(static_cast<int>(getpid())));

    auto now = boost::posix_time::second_clock::universal_time();
    if (!start_.is_not_a_date_time()) {
        auto uptime = now - start_;
        status->set("uptime", Element::create(uptime.total_seconds()));
    }

    auto last_commit = process_->getCfgMgr()->getContext()->getLastCommitTime();
    if (!last_commit.is_not_a_date_time()) {
        auto reload = now - last_commit;
        status->set("reload", Element::create(reload.total_seconds()));
    }

    return (createAnswer(CONTROL_RESULT_SUCCESS, status));
}

ConstElementPtr
DControllerBase::versionGetHandler(const std::string&, ConstElementPtr) {
    ConstElementPtr answer;

    // For version-get put the extended version in arguments
    ElementPtr extended = Element::create(getVersion(true));
    ElementPtr arguments = Element::createMap();
    arguments->set("extended", extended);
    answer = createAnswer(CONTROL_RESULT_SUCCESS, getVersion(false), arguments);
    return (answer);
}

ConstElementPtr
DControllerBase::buildReportHandler(const std::string&, ConstElementPtr) {
    return (createAnswer(CONTROL_RESULT_SUCCESS, isc::detail::getConfigReport()));
}

ConstElementPtr
DControllerBase::shutdownHandler(const std::string&, ConstElementPtr args) {
    // Shutdown is universal.  If its not that, then try it as
    // a custom command supported by the derivation.  If that
    // doesn't pan out either, than send to it the application
    // as it may be supported there.

    int exit_value = EXIT_SUCCESS;
    if (args) {
        // @todo Should we go ahead and shutdown even if the args are invalid?
        if (args->getType() != Element::map) {
            return (createAnswer(CONTROL_RESULT_ERROR, "Argument must be a map"));
        }

        ConstElementPtr param = args->get("exit-value");
        if (param) {
            if (param->getType() != Element::integer) {
                return (createAnswer(CONTROL_RESULT_ERROR,
                                     "parameter 'exit-value' is not an integer"));
            }

            exit_value = param->intValue();
        }
    }

    setExitValue(exit_value);
    return (shutdownProcess(args));
}

ConstElementPtr
DControllerBase::shutdownProcess(ConstElementPtr args) {
    if (process_) {
        return (process_->shutdown(args));
    }

    // Not really a failure, but this condition is worth noting. In reality
    // it should be pretty hard to cause this.
    LOG_WARN(dctl_logger, DCTL_NOT_RUNNING).arg(app_name_);
    return (createAnswer(CONTROL_RESULT_SUCCESS, "Process has not been initialized"));
}

void
DControllerBase::initSignalHandling() {
    /// @todo block everything we don't handle

    // Create our signal set.
    io_signal_set_.reset(new IOSignalSet(io_service_,
                                         std::bind(&DControllerBase::
                                                   processSignal,
                                                   this, ph::_1)));
    // Register for the signals we wish to handle.
    io_signal_set_->add(SIGHUP);
    io_signal_set_->add(SIGINT);
    io_signal_set_->add(SIGTERM);
}

void
DControllerBase::processSignal(int signum) {
    switch (signum) {
        case SIGHUP:
        {
            LOG_INFO(dctl_logger, DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD)
                     .arg(signum).arg(getConfigFile());
            int rcode;
            ConstElementPtr comment = parseAnswer(rcode, configFromFile());
            if (rcode != 0) {
                LOG_ERROR(dctl_logger, DCTL_CFG_FILE_RELOAD_ERROR)
                          .arg(comment->stringValue());
            }

            break;
        }

        case SIGINT:
        case SIGTERM:
        {
            LOG_DEBUG(dctl_logger, isc::log::DBGLVL_START_SHUT,
                      DCTL_SHUTDOWN_SIGNAL_RECVD).arg(signum);
            ElementPtr arg_set;
            shutdownHandler(SHUT_DOWN_COMMAND, arg_set);
            break;
        }

        default:
            LOG_WARN(dctl_logger, DCTL_UNSUPPORTED_SIGNAL).arg(signum);
            break;
    }
}

void
DControllerBase::usage(const std::string & text) {
    if (text != "") {
        std::cerr << "Usage error: " << text << std::endl;
    }

    std::cerr << "Usage: " << bin_name_ << std::endl
              << "  -v: print version number and exit" << std::endl
              << "  -V: print extended version information and exit"
              << std::endl
              << "  -W: display the configuration report and exit"
              << std::endl
              << "  -d: optional, verbose output " << std::endl
              << "  -c <config file name> : mandatory,"
              << " specify name of configuration file" << std::endl
              << "  -t <config file name> : check the"
              << " configuration file and exit" << std::endl
              << "  -X: disables security restrictions" << std::endl;

    // add any derivation specific usage
    std::cerr << getUsageText() << std::endl;
}

DControllerBase::~DControllerBase() {
    // Explicitly unload hooks
    HooksManager::prepareUnloadLibraries();
    if (!HooksManager::unloadLibraries()) {
        auto names = HooksManager::getLibraryNames();
        std::string msg;
        if (!names.empty()) {
            msg = names[0];
            for (size_t i = 1; i < names.size(); ++i) {
                msg += std::string(", ") + names[i];
            }
        }
        LOG_ERROR(dctl_logger, DCTL_UNLOAD_LIBRARIES_ERROR).arg(msg);
    }

    IOServiceMgr::instance().clearIOServices();

    io_signal_set_.reset();
    try {
        getIOService()->poll();
    } catch (...) {
        // Don't want to throw exceptions from the destructor. The process
        // is shutting down anyway.
    }
}

std::string
DControllerBase::getVersion(bool extended) {
    std::stringstream tmp;

    tmp << VERSION;
    if (extended) {
        tmp << " (" << EXTENDED_VERSION << ")" << std::endl;
        tmp << "premium: " << PREMIUM_EXTENDED_VERSION << std::endl;
        tmp << "linked with:" << std::endl;
        tmp << "- " << isc::log::Logger::getVersion() << std::endl;
        tmp << "- " << isc::cryptolink::CryptoLink::getVersion();
    }

    return (tmp.str());
}

} // end of namespace isc::process

} // end of namespace isc
