view libirccd/irccd/daemon/irccd.cpp @ 745:903415e8ee2e

Tests: add error tests in irccdctl
author David Demelier <markand@malikania.fr>
date Wed, 01 Aug 2018 12:43:16 +0200
parents d3660cbf2f3c
children c216d148558d
line wrap: on
line source

/*
 * irccd.cpp -- main irccd class
 *
 * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <fstream>

#include <boost/predef/os.h>

#include <irccd/string_util.hpp>
#include <irccd/system.hpp>

#include "irccd.hpp"
#include "logger.hpp"

#include "service/plugin_service.hpp"
#include "service/rule_service.hpp"
#include "service/server_service.hpp"
#include "service/transport_service.hpp"

namespace irccd {

namespace {

class format_filter : public logger::filter {
private:
    std::string info_;
    std::string warning_;
    std::string debug_;

    auto convert(const std::string&,
                 std::string_view,
                 std::string_view,
                 std::string_view) const -> std::string;

public:
    format_filter(std::string info, std::string warning, std::string debug) noexcept;

    auto pre_debug(std::string_view,
                   std::string_view,
                   std::string_view) const -> std::string override;

    auto pre_info(std::string_view,
                  std::string_view,
                  std::string_view) const -> std::string override;

    auto pre_warning(std::string_view,
                     std::string_view,
                     std::string_view) const -> std::string override;
};

auto format_filter::convert(const std::string& tmpl,
                            std::string_view category,
                            std::string_view component,
                            std::string_view message) const -> std::string
{
    if (tmpl.empty())
        return pre(category, component, message);

    string_util::subst params;

    params.flags &= ~(string_util::subst_flags::irc_attrs);
    params.flags |= string_util::subst_flags::shell_attrs;
    params.keywords.emplace("category", std::string(category));
    params.keywords.emplace("component", std::string(component));
    params.keywords.emplace("message", std::string(message));

    return string_util::format(tmpl, params);
}

format_filter::format_filter(std::string info, std::string warning, std::string debug) noexcept
    : info_(std::move(info))
    , warning_(std::move(warning))
    , debug_(std::move(debug))
{
}

auto format_filter::pre_debug(std::string_view category,
                              std::string_view component,
                              std::string_view message) const -> std::string
{
    return convert(debug_, category, component, message);
}

auto format_filter::pre_info(std::string_view category,
                             std::string_view component,
                             std::string_view message) const -> std::string
{
    return convert(info_, category, component, message);
}

auto format_filter::pre_warning(std::string_view category,
                                std::string_view component,
                                std::string_view message) const -> std::string
{
    return convert(warning_, category, component, message);
}

} // !namespace

void irccd::load_logs_file(const ini::section& sc)
{
    /*
     * TODO: improve that with CMake options.
     */
#if BOOST_OS_WINDOWS
    std::string normal = "log.txt";
    std::string errors = "errors.txt";
#else
    std::string normal = "/var/log/irccd/log.txt";
    std::string errors = "/var/log/irccd/errors.txt";
#endif

    ini::section::const_iterator it;

    if ((it = sc.find("path-logs")) != sc.end())
        normal = it->value();
    if ((it = sc.find("path-errors")) != sc.end())
        errors = it->value();

    try {
        sink_ = std::make_unique<logger::file_sink>(std::move(normal), std::move(errors));
    } catch (const std::exception& ex) {
        sink_->warning("logs", "") << ex.what() << std::endl;
    }
}

void irccd::load_logs_syslog()
{
#if defined(HAVE_SYSLOG)
    sink_ = std::make_unique<logger::syslog_sink>();
#else
    sink_->warning() << "logs: syslog is not available on this platform" << std::endl;
#endif // !HAVE_SYSLOG
}

void irccd::load_logs()
{
    const auto sc = config_.get("logs");

    if (sc.empty())
        return;

    sink_->set_verbose(string_util::is_identifier(sc.get("verbose").value()));

    const auto type = sc.get("type").value();

    if (!type.empty()) {
        // Console is the default, no test case.
        if (type == "file")
            load_logs_file(sc);
        else if (type == "syslog")
            load_logs_syslog();
        else if (type != "console")
            sink_->warning("logs", "") << "invalid log type '" << type << std::endl;
    }
}

void irccd::load_formats()
{
    const auto sc = config_.get("format");

    if (sc.empty())
        return;

    sink_->set_filter(std::make_unique<format_filter>(
        sc.get("info").value(),
        sc.get("warning").value(),
        sc.get("debug").value()
    ));
}

irccd::irccd(boost::asio::io_service& service, std::string config)
    : config_(std::move(config))
    , service_(service)
    , sink_(std::make_unique<logger::console_sink>())
    , server_service_(std::make_unique<server_service>(*this))
    , tpt_service_(std::make_unique<transport_service>(*this))
    , rule_service_(std::make_unique<rule_service>(*this))
    , plugin_service_(std::make_unique<plugin_service>(*this))
{
}

irccd::~irccd() = default;

auto irccd::get_config() const noexcept -> const config&
{
    return config_;
}

void irccd::set_config(config cfg) noexcept
{
    config_ = std::move(cfg);
}

auto irccd::get_service() const noexcept -> const boost::asio::io_service&
{
    return service_;
}

auto irccd::get_service() noexcept -> boost::asio::io_service&
{
    return service_;
}

auto irccd::get_log() const noexcept -> const logger::sink&
{
    return *sink_;
}

auto irccd::get_log() noexcept -> logger::sink&
{
    return *sink_;
}

auto irccd::servers() noexcept -> server_service&
{
    return *server_service_;
}

auto irccd::transports() noexcept -> transport_service&
{
    return *tpt_service_;
}

auto irccd::rules() noexcept -> rule_service&
{
    return *rule_service_;
}

auto irccd::plugins() noexcept -> plugin_service&
{
    return *plugin_service_;
}

void irccd::set_log(std::unique_ptr<logger::sink> sink) noexcept
{
    assert(sink);

    sink_ = std::move(sink);
}

void irccd::load() noexcept
{
    /*
     * Order matters, please be careful when changing this.
     *
     * 1. Open logs as early as possible to use the defined outputs on any
     *    loading errors.
     */

    // [logs] and [format] sections.
    load_logs();
    load_formats();

    if (!loaded_)
        sink_->info("irccd", "") << "loading configuration from " << config_.get_path() << std::endl;
    else
        sink_->info("irccd", "") << "reloading configuration" << std::endl;

    if (!loaded_)
        tpt_service_->load(config_);

    server_service_->load(config_);
    plugin_service_->load(config_);
    rule_service_->load(config_);

    // Mark as loaded.
    loaded_ = true;
}

auto irccd_category() noexcept -> const std::error_category&
{
    static const class category : public std::error_category {
    public:
        const char* name() const noexcept override
        {
            return "irccd";
        }

        std::string message(int e) const override
        {
            switch (static_cast<irccd_error::error>(e)) {
            case irccd_error::error::not_irccd:
                return "daemon is not irccd instance";
            case irccd_error::error::incompatible_version:
                return "major version is incompatible";
            case irccd_error::error::auth_required:
                return "authentication is required";
            case irccd_error::error::invalid_auth:
                return "invalid authentication";
            case irccd_error::error::invalid_message:
                return "invalid message";
            case irccd_error::error::invalid_command:
                return "invalid command";
            case irccd_error::error::incomplete_message:
                return "command requires more arguments";
            default:
                return "no error";
            }
        }
    } category;

    return category;
}

auto make_error_code(irccd_error::error e) noexcept -> std::error_code
{
    return {static_cast<int>(e), irccd_category()};
}

} // !irccd